Ecoer Logo
theolujay

@theolujay

25

Software Engineer. Building stuff. Making progress.

hive.blog/@theolujay
VOTING POWER0.00%
DOWNVOTE POWER0.00%
RESOURCE CREDITS100.00%
REPUTATION PROGRESS0.00%
Net Worth
0.000USD
HIVE
0.000HIVE
HBD
0.000HBD
Own HP
0.000HP

Detailed Balance

HIVE
balance
0.000HIVE
market_balance
0.000HIVE
savings_balance
0.000HIVE
reward_hive_balance
0.000HIVE
HIVE POWER
Own HP
0.000HP
Delegated Out
0.000HP
Delegation In
0.000HP
Effective Power
0.000HP
Reward HP (pending)
0.725HP
HBD
hbd_balance
0.000HBD
hbd_conversions
0.000HBD
hbd_market_balance
0.000HBD
savings_hbd_balance
0.000HBD
reward_hbd_balance
0.000HBD
{
  "balance": "0.000 HIVE",
  "savings_balance": "0.000 HIVE",
  "reward_hive_balance": "0.000 HIVE",
  "vesting_shares": "0.000000 VESTS",
  "delegated_vesting_shares": "0.000000 VESTS",
  "received_vesting_shares": "0.000000 VESTS",
  "hbd_balance": "0.000 HBD",
  "savings_hbd_balance": "0.000 HBD",
  "reward_hbd_balance": "0.000 HBD"
}

Account Info

nametheolujay
id2589217
rank0
reputation0
created2025-09-23T13:00:18
recovery_accountolujay
proxyNone
invited_bynull
post_count7
comment_count0
lifetime_vote_count0
witnesses_voted_for0
last_post2026-01-02T13:01:00
last_root_post2026-01-02T13:01:00
last_vote_time1970-01-01T00:00:00
proxied_vsf_votes0, 0, 0, 0
can_vote1
voting_power0
delayed_votesNone
governance_vote_expiration_ts1969-12-31T23:59:59
balance0.000 HIVE
savings_balance0.000 HIVE
hbd_balance0.000 HBD
savings_hbd_balance0.000 HBD
vesting_shares0.000000 VESTS
delegated_vesting_shares0.000000 VESTS
received_vesting_shares0.000000 VESTS
reward_vesting_balance1190.867863 VESTS
vesting_balance0.000 HIVE
vesting_withdraw_rate0.000000 VESTS
next_vesting_withdrawal1969-12-31T23:59:59
withdrawn0
to_withdraw0
withdraw_routes0
savings_withdraw_requests0
last_account_recovery1970-01-01T00:00:00
reset_accountnull
last_owner_update1970-01-01T00:00:00
last_account_update2025-12-30T12:02:27
minedNo
hbd_seconds0
hbd_last_interest_payment1970-01-01T00:00:00
savings_hbd_last_interest_payment1970-01-01T00:00:00
{
  "active": {
    "account_auths": [],
    "key_auths": [
      [
        "STM5WSm5PM4w1DznQzKDL6Hg5Yc7PdTK6JWeEuTY1EGTxm2MtJMat",
        1
      ]
    ],
    "weight_threshold": 1
  },
  "balance": "0.000 HIVE",
  "can_vote": true,
  "comment_count": 0,
  "created": "2025-09-23T13:00:18",
  "curation_rewards": 0,
  "delayed_votes": [],
  "delegated_vesting_shares": "0.000000 VESTS",
  "downvote_manabar": {
    "current_mana": 0,
    "last_update_time": 1758632415
  },
  "governance_vote_expiration_ts": "1969-12-31T23:59:59",
  "guest_bloggers": [],
  "hbd_balance": "0.000 HBD",
  "hbd_last_interest_payment": "1970-01-01T00:00:00",
  "hbd_seconds": "0",
  "hbd_seconds_last_update": "1970-01-01T00:00:00",
  "id": 2589217,
  "json_metadata": "",
  "last_account_recovery": "1970-01-01T00:00:00",
  "last_account_update": "2025-12-30T12:02:27",
  "last_owner_update": "1970-01-01T00:00:00",
  "last_post": "2026-01-02T13:01:00",
  "last_root_post": "2026-01-02T13:01:00",
  "last_vote_time": "1970-01-01T00:00:00",
  "lifetime_vote_count": 0,
  "market_history": [],
  "memo_key": "STM7qhEtvNyXVg5Y4X3RxxTCbBo3DwZvAYRPEqnmGYZnnRZVcxHpY",
  "mined": false,
  "name": "theolujay",
  "next_vesting_withdrawal": "1969-12-31T23:59:59",
  "open_recurrent_transfers": 0,
  "other_history": [],
  "owner": {
    "account_auths": [],
    "key_auths": [
      [
        "STM8CYNnDbbBvbVdTkHK9R2CLNpVCSkPAELVL3odf1UpTKjX4tzUF",
        1
      ]
    ],
    "weight_threshold": 1
  },
  "pending_claimed_accounts": 0,
  "pending_transfers": 0,
  "post_bandwidth": 0,
  "post_count": 7,
  "post_history": [],
  "post_voting_power": "0.000000 VESTS",
  "posting": {
    "account_auths": [
      [
        "peakd.app",
        1
      ]
    ],
    "key_auths": [
      [
        "STM51wtCNy7STzYt8VdimooXYaBRCTqhZDmTnTPb4Z13pr5vP5mh5",
        1
      ]
    ],
    "weight_threshold": 1
  },
  "posting_json_metadata": "{\"profile\":{\"name\":\"Joseph Ezekiel\",\"about\":\"Software Engineer. Building stuff. Making progress.\",\"location\":\"Galvan Prime\",\"website\":\"theolujay.dev\",\"profile_image\":\"https://files.peakd.com/file/peakd-hive/theolujay/1767023094352.jpg\",\"cover_image\":\"https://files.peakd.com/file/peakd-hive/theolujay/1767023214941.jpg\",\"version\":2,\"portfolio\":\"enabled\"}}",
  "posting_rewards": 725,
  "previous_owner_update": "1970-01-01T00:00:00",
  "proxied_vsf_votes": [
    0,
    0,
    0,
    0
  ],
  "proxy": "",
  "received_vesting_shares": "0.000000 VESTS",
  "recovery_account": "olujay",
  "reputation": 0,
  "reset_account": "null",
  "reward_hbd_balance": "0.000 HBD",
  "reward_hive_balance": "0.000 HIVE",
  "reward_vesting_balance": "1190.867863 VESTS",
  "reward_vesting_hive": "0.725 HIVE",
  "savings_balance": "0.000 HIVE",
  "savings_hbd_balance": "0.000 HBD",
  "savings_hbd_last_interest_payment": "1970-01-01T00:00:00",
  "savings_hbd_seconds": "0",
  "savings_hbd_seconds_last_update": "1970-01-01T00:00:00",
  "savings_withdraw_requests": 0,
  "tags_usage": [],
  "to_withdraw": 0,
  "transfer_history": [],
  "vesting_balance": "0.000 HIVE",
  "vesting_shares": "0.000000 VESTS",
  "vesting_withdraw_rate": "0.000000 VESTS",
  "vote_history": [],
  "voting_manabar": {
    "current_mana": 0,
    "last_update_time": 1758632415
  },
  "voting_power": 0,
  "withdraw_routes": 0,
  "withdrawn": 0,
  "witness_votes": [],
  "witnesses_voted_for": 0,
  "rank": 0
}

Withdraw Routes

IncomingOutgoing
Empty
Empty
{
  "incoming": [],
  "outgoing": []
}
From Date
To Date
2026/01/09 13:01:00
authortheolujay
permlinkmulti-stage-docker-uv-python
Transaction InfoBlock #102784386/Virtual Operation 4294967295:6
View Raw JSON Data
{
  "block": 102784386,
  "op": [
    "comment_payout_update",
    {
      "author": "theolujay",
      "permlink": "multi-stage-docker-uv-python"
    }
  ],
  "op_in_trx": 6,
  "timestamp": "2026-01-09T13:01:00",
  "trx_id": "0000000000000000000000000000000000000000",
  "trx_in_block": 4294967295,
  "virtual_op": true
}
theolujayreceived 0.037 HBD reward share for multi-stage-docker-uv-python
2026/01/09 13:01:00
authortheolujay
author rewards176
beneficiary payout value0.000 HBD
curator payout value0.018 HBD
payout0.037 HBD
permlinkmulti-stage-docker-uv-python
total payout value0.018 HBD
Transaction InfoBlock #102784386/Virtual Operation 4294967295:5
View Raw JSON Data
{
  "block": 102784386,
  "op": [
    "comment_reward",
    {
      "author": "theolujay",
      "author_rewards": 176,
      "beneficiary_payout_value": "0.000 HBD",
      "curator_payout_value": "0.018 HBD",
      "payout": "0.037 HBD",
      "permlink": "multi-stage-docker-uv-python",
      "total_payout_value": "0.018 HBD"
    }
  ],
  "op_in_trx": 5,
  "timestamp": "2026-01-09T13:01:00",
  "trx_id": "0000000000000000000000000000000000000000",
  "trx_in_block": 4294967295,
  "virtual_op": true
}
theolujayreceived 0.178 HP author reward for @theolujay / multi-stage-docker-uv-python
2026/01/09 13:01:00
authortheolujay
curators vesting payout287.413930 VESTS
hbd payout0.000 HBD
hive payout0.000 HIVE
payout must be claimedtrue
permlinkmulti-stage-docker-uv-python
vesting payout289.056295 VESTS
Transaction InfoBlock #102784386/Virtual Operation 4294967295:4
View Raw JSON Data
{
  "block": 102784386,
  "op": [
    "author_reward",
    {
      "author": "theolujay",
      "curators_vesting_payout": "287.413930 VESTS",
      "hbd_payout": "0.000 HBD",
      "hive_payout": "0.000 HIVE",
      "payout_must_be_claimed": true,
      "permlink": "multi-stage-docker-uv-python",
      "vesting_payout": "289.056295 VESTS"
    }
  ],
  "op_in_trx": 4,
  "timestamp": "2026-01-09T13:01:00",
  "trx_id": "0000000000000000000000000000000000000000",
  "trx_in_block": 4294967295,
  "virtual_op": true
}
2026/01/08 13:00:00
authortheolujay
permlinkpg-backup-docker-s3-slack
Transaction InfoBlock #102755636/Virtual Operation 4294967295:136
View Raw JSON Data
{
  "block": 102755636,
  "op": [
    "comment_payout_update",
    {
      "author": "theolujay",
      "permlink": "pg-backup-docker-s3-slack"
    }
  ],
  "op_in_trx": 136,
  "timestamp": "2026-01-08T13:00:00",
  "trx_id": "0000000000000000000000000000000000000000",
  "trx_in_block": 4294967295,
  "virtual_op": true
}
theolujayreceived 0.029 HBD reward share for pg-backup-docker-s3-slack
2026/01/08 13:00:00
authortheolujay
author rewards146
beneficiary payout value0.000 HBD
curator payout value0.014 HBD
payout0.029 HBD
permlinkpg-backup-docker-s3-slack
total payout value0.014 HBD
Transaction InfoBlock #102755636/Virtual Operation 4294967295:135
View Raw JSON Data
{
  "block": 102755636,
  "op": [
    "comment_reward",
    {
      "author": "theolujay",
      "author_rewards": 146,
      "beneficiary_payout_value": "0.000 HBD",
      "curator_payout_value": "0.014 HBD",
      "payout": "0.029 HBD",
      "permlink": "pg-backup-docker-s3-slack",
      "total_payout_value": "0.014 HBD"
    }
  ],
  "op_in_trx": 135,
  "timestamp": "2026-01-08T13:00:00",
  "trx_id": "0000000000000000000000000000000000000000",
  "trx_in_block": 4294967295,
  "virtual_op": true
}
theolujayreceived 0.148 HP author reward for @theolujay / pg-backup-docker-s3-slack
2026/01/08 13:00:00
authortheolujay
curators vesting payout238.159916 VESTS
hbd payout0.000 HBD
hive payout0.000 HIVE
payout must be claimedtrue
permlinkpg-backup-docker-s3-slack
vesting payout239.802399 VESTS
Transaction InfoBlock #102755636/Virtual Operation 4294967295:134
View Raw JSON Data
{
  "block": 102755636,
  "op": [
    "author_reward",
    {
      "author": "theolujay",
      "curators_vesting_payout": "238.159916 VESTS",
      "hbd_payout": "0.000 HBD",
      "hive_payout": "0.000 HIVE",
      "payout_must_be_claimed": true,
      "permlink": "pg-backup-docker-s3-slack",
      "vesting_payout": "239.802399 VESTS"
    }
  ],
  "op_in_trx": 134,
  "timestamp": "2026-01-08T13:00:00",
  "trx_id": "0000000000000000000000000000000000000000",
  "trx_in_block": 4294967295,
  "virtual_op": true
}
theolujayupdated payout for re-hivebuzz-t85dzh
2026/01/07 18:57:42
authortheolujay
permlinkre-hivebuzz-t85dzh
Transaction InfoBlock #102734045/Virtual Operation 4294967295:3
View Raw JSON Data
{
  "block": 102734045,
  "op": [
    "comment_payout_update",
    {
      "author": "theolujay",
      "permlink": "re-hivebuzz-t85dzh"
    }
  ],
  "op_in_trx": 3,
  "timestamp": "2026-01-07T18:57:42",
  "trx_id": "0000000000000000000000000000000000000000",
  "trx_in_block": 4294967295,
  "virtual_op": true
}
2026/01/07 13:00:00
authortheolujay
permlinkhow-cloudflare-fixed-my-geo-locked-blog
Transaction InfoBlock #102726909/Virtual Operation 4294967295:98
View Raw JSON Data
{
  "block": 102726909,
  "op": [
    "comment_payout_update",
    {
      "author": "theolujay",
      "permlink": "how-cloudflare-fixed-my-geo-locked-blog"
    }
  ],
  "op_in_trx": 98,
  "timestamp": "2026-01-07T13:00:00",
  "trx_id": "0000000000000000000000000000000000000000",
  "trx_in_block": 4294967295,
  "virtual_op": true
}
theolujayreceived 0.042 HBD reward share for how-cloudflare-fixed-my-geo-locked-blog
2026/01/07 13:00:00
authortheolujay
author rewards214
beneficiary payout value0.000 HBD
curator payout value0.021 HBD
payout0.042 HBD
permlinkhow-cloudflare-fixed-my-geo-locked-blog
total payout value0.021 HBD
Transaction InfoBlock #102726909/Virtual Operation 4294967295:97
View Raw JSON Data
{
  "block": 102726909,
  "op": [
    "comment_reward",
    {
      "author": "theolujay",
      "author_rewards": 214,
      "beneficiary_payout_value": "0.000 HBD",
      "curator_payout_value": "0.021 HBD",
      "payout": "0.042 HBD",
      "permlink": "how-cloudflare-fixed-my-geo-locked-blog",
      "total_payout_value": "0.021 HBD"
    }
  ],
  "op_in_trx": 97,
  "timestamp": "2026-01-07T13:00:00",
  "trx_id": "0000000000000000000000000000000000000000",
  "trx_in_block": 4294967295,
  "virtual_op": true
}
theolujayreceived 0.217 HP author reward for @theolujay / how-cloudflare-fixed-my-geo-locked-blog
2026/01/07 13:00:00
authortheolujay
curators vesting payout346.588540 VESTS
hbd payout0.000 HBD
hive payout0.000 HIVE
payout must be claimedtrue
permlinkhow-cloudflare-fixed-my-geo-locked-blog
vesting payout351.516339 VESTS
Transaction InfoBlock #102726909/Virtual Operation 4294967295:96
View Raw JSON Data
{
  "block": 102726909,
  "op": [
    "author_reward",
    {
      "author": "theolujay",
      "curators_vesting_payout": "346.588540 VESTS",
      "hbd_payout": "0.000 HBD",
      "hive_payout": "0.000 HIVE",
      "payout_must_be_claimed": true,
      "permlink": "how-cloudflare-fixed-my-geo-locked-blog",
      "vesting_payout": "351.516339 VESTS"
    }
  ],
  "op_in_trx": 96,
  "timestamp": "2026-01-07T13:00:00",
  "trx_id": "0000000000000000000000000000000000000000",
  "trx_in_block": 4294967295,
  "virtual_op": true
}
2026/01/06 13:00:00
authortheolujay
permlinkpractical-habits-for-productive-python-development
Transaction InfoBlock #102698174/Virtual Operation 4294967295:93
View Raw JSON Data
{
  "block": 102698174,
  "op": [
    "comment_payout_update",
    {
      "author": "theolujay",
      "permlink": "practical-habits-for-productive-python-development"
    }
  ],
  "op_in_trx": 93,
  "timestamp": "2026-01-06T13:00:00",
  "trx_id": "0000000000000000000000000000000000000000",
  "trx_in_block": 4294967295,
  "virtual_op": true
}
theolujayupdated payout for initial-commit
2026/01/05 16:24:00
authortheolujay
permlinkinitial-commit
Transaction InfoBlock #102673511/Virtual Operation 4294967295:7
View Raw JSON Data
{
  "block": 102673511,
  "op": [
    "comment_payout_update",
    {
      "author": "theolujay",
      "permlink": "initial-commit"
    }
  ],
  "op_in_trx": 7,
  "timestamp": "2026-01-05T16:24:00",
  "trx_id": "0000000000000000000000000000000000000000",
  "trx_in_block": 4294967295,
  "virtual_op": true
}
theolujayreceived 0.036 HBD reward share for initial-commit
2026/01/05 16:24:00
authortheolujay
author rewards189
beneficiary payout value0.000 HBD
curator payout value0.018 HBD
payout0.036 HBD
permlinkinitial-commit
total payout value0.018 HBD
Transaction InfoBlock #102673511/Virtual Operation 4294967295:6
View Raw JSON Data
{
  "block": 102673511,
  "op": [
    "comment_reward",
    {
      "author": "theolujay",
      "author_rewards": 189,
      "beneficiary_payout_value": "0.000 HBD",
      "curator_payout_value": "0.018 HBD",
      "payout": "0.036 HBD",
      "permlink": "initial-commit",
      "total_payout_value": "0.018 HBD"
    }
  ],
  "op_in_trx": 6,
  "timestamp": "2026-01-05T16:24:00",
  "trx_id": "0000000000000000000000000000000000000000",
  "trx_in_block": 4294967295,
  "virtual_op": true
}
theolujayreceived 0.191 HP author reward for @theolujay / initial-commit
2026/01/05 16:24:00
authortheolujay
curators vesting payout303.921553 VESTS
hbd payout0.000 HBD
hive payout0.000 HIVE
payout must be claimedtrue
permlinkinitial-commit
vesting payout310.492830 VESTS
Transaction InfoBlock #102673511/Virtual Operation 4294967295:5
View Raw JSON Data
{
  "block": 102673511,
  "op": [
    "author_reward",
    {
      "author": "theolujay",
      "curators_vesting_payout": "303.921553 VESTS",
      "hbd_payout": "0.000 HBD",
      "hive_payout": "0.000 HIVE",
      "payout_must_be_claimed": true,
      "permlink": "initial-commit",
      "vesting_payout": "310.492830 VESTS"
    }
  ],
  "op_in_trx": 5,
  "timestamp": "2026-01-05T16:24:00",
  "trx_id": "0000000000000000000000000000000000000000",
  "trx_in_block": 4294967295,
  "virtual_op": true
}
magic.byteeffective vote applied for @theolujay / pg-backup-docker-s3-slack
2026/01/04 06:28:09
authortheolujay
pending payout0.028 HBD
permlinkpg-backup-docker-s3-slack
rshares0
total vote weight235731208671
votermagic.byte
weight0 (0.00%)
Transaction InfoBlock #102632879/Trx aeb53efafce9eef12daf8fd194e47ce40992f039
View Raw JSON Data
{
  "block": 102632879,
  "op": [
    "effective_comment_vote",
    {
      "author": "theolujay",
      "pending_payout": "0.028 HBD",
      "permlink": "pg-backup-docker-s3-slack",
      "rshares": 0,
      "total_vote_weight": 235731208671,
      "voter": "magic.byte",
      "weight": 0
    }
  ],
  "op_in_trx": 1,
  "timestamp": "2026-01-04T06:28:09",
  "trx_id": "aeb53efafce9eef12daf8fd194e47ce40992f039",
  "trx_in_block": 12,
  "virtual_op": true
}
2026/01/04 06:28:09
authortheolujay
permlinkpg-backup-docker-s3-slack
votermagic.byte
weight10000 (100.00%)
Transaction InfoBlock #102632879/Trx aeb53efafce9eef12daf8fd194e47ce40992f039
View Raw JSON Data
{
  "block": 102632879,
  "op": [
    "vote",
    {
      "author": "theolujay",
      "permlink": "pg-backup-docker-s3-slack",
      "voter": "magic.byte",
      "weight": 10000
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2026-01-04T06:28:09",
  "trx_id": "aeb53efafce9eef12daf8fd194e47ce40992f039",
  "trx_in_block": 12,
  "virtual_op": false
}
2026/01/02 20:49:39
authortheolujay
pending payout0.034 HBD
permlinkmulti-stage-docker-uv-python
rshares947035228
total vote weight282857333166
voterlolz.byte
weight947035228
Transaction InfoBlock #102592602/Trx 1d203f325f9fe69c7bbb61847a667ebe27de4bcf
View Raw JSON Data
{
  "block": 102592602,
  "op": [
    "effective_comment_vote",
    {
      "author": "theolujay",
      "pending_payout": "0.034 HBD",
      "permlink": "multi-stage-docker-uv-python",
      "rshares": 947035228,
      "total_vote_weight": 282857333166,
      "voter": "lolz.byte",
      "weight": 947035228
    }
  ],
  "op_in_trx": 1,
  "timestamp": "2026-01-02T20:49:39",
  "trx_id": "1d203f325f9fe69c7bbb61847a667ebe27de4bcf",
  "trx_in_block": 13,
  "virtual_op": true
}
2026/01/02 20:49:39
authortheolujay
permlinkmulti-stage-docker-uv-python
voterlolz.byte
weight10000 (100.00%)
Transaction InfoBlock #102592602/Trx 1d203f325f9fe69c7bbb61847a667ebe27de4bcf
View Raw JSON Data
{
  "block": 102592602,
  "op": [
    "vote",
    {
      "author": "theolujay",
      "permlink": "multi-stage-docker-uv-python",
      "voter": "lolz.byte",
      "weight": 10000
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2026-01-02T20:49:39",
  "trx_id": "1d203f325f9fe69c7bbb61847a667ebe27de4bcf",
  "trx_in_block": 13,
  "virtual_op": false
}
2026/01/02 16:01:51
authortheolujay
pending payout0.033 HBD
permlinkmulti-stage-docker-uv-python
rshares0
total vote weight281910297938
votermagic.byte
weight0 (0.00%)
Transaction InfoBlock #102586857/Trx 348a574ed84d6fa49e7c7e74804d701fe5e7d7c5
View Raw JSON Data
{
  "block": 102586857,
  "op": [
    "effective_comment_vote",
    {
      "author": "theolujay",
      "pending_payout": "0.033 HBD",
      "permlink": "multi-stage-docker-uv-python",
      "rshares": 0,
      "total_vote_weight": 281910297938,
      "voter": "magic.byte",
      "weight": 0
    }
  ],
  "op_in_trx": 1,
  "timestamp": "2026-01-02T16:01:51",
  "trx_id": "348a574ed84d6fa49e7c7e74804d701fe5e7d7c5",
  "trx_in_block": 2,
  "virtual_op": true
}
2026/01/02 16:01:51
authortheolujay
permlinkmulti-stage-docker-uv-python
votermagic.byte
weight10000 (100.00%)
Transaction InfoBlock #102586857/Trx 348a574ed84d6fa49e7c7e74804d701fe5e7d7c5
View Raw JSON Data
{
  "block": 102586857,
  "op": [
    "vote",
    {
      "author": "theolujay",
      "permlink": "multi-stage-docker-uv-python",
      "voter": "magic.byte",
      "weight": 10000
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2026-01-02T16:01:51",
  "trx_id": "348a574ed84d6fa49e7c7e74804d701fe5e7d7c5",
  "trx_in_block": 2,
  "virtual_op": false
}
akdxeffective vote applied for @theolujay / multi-stage-docker-uv-python
2026/01/02 13:38:21
authortheolujay
pending payout0.033 HBD
permlinkmulti-stage-docker-uv-python
rshares281763154931
total vote weight281910297938
voterakdx
weight281763154931
Transaction InfoBlock #102583993/Trx f03cef7d242518a6c8d3a64125c9ef0c187bc00f
View Raw JSON Data
{
  "block": 102583993,
  "op": [
    "effective_comment_vote",
    {
      "author": "theolujay",
      "pending_payout": "0.033 HBD",
      "permlink": "multi-stage-docker-uv-python",
      "rshares": 281763154931,
      "total_vote_weight": 281910297938,
      "voter": "akdx",
      "weight": 281763154931
    }
  ],
  "op_in_trx": 1,
  "timestamp": "2026-01-02T13:38:21",
  "trx_id": "f03cef7d242518a6c8d3a64125c9ef0c187bc00f",
  "trx_in_block": 13,
  "virtual_op": true
}
2026/01/02 13:38:21
authortheolujay
permlinkmulti-stage-docker-uv-python
voterakdx
weight6000 (60.00%)
Transaction InfoBlock #102583993/Trx f03cef7d242518a6c8d3a64125c9ef0c187bc00f
View Raw JSON Data
{
  "block": 102583993,
  "op": [
    "vote",
    {
      "author": "theolujay",
      "permlink": "multi-stage-docker-uv-python",
      "voter": "akdx",
      "weight": 6000
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2026-01-02T13:38:21",
  "trx_id": "f03cef7d242518a6c8d3a64125c9ef0c187bc00f",
  "trx_in_block": 13,
  "virtual_op": false
}
2026/01/02 13:01:12
authorwe-are-ai
body<div style="text-align: center;"> https://images.ecency.com/DQmTwzjpExG956nSeyoxGM5Z3aqTSGjxmHoyShSYSCfRNKF/image.png </div> I am an AI curator currently being programmed; if I voted for you, it's because your post respects certain curation rules. help us put a 1% vote
json metadata{"tags":["curation","ai"],"app":"we-are-ai/1.0"}
parent authortheolujay
parent permlinkmulti-stage-docker-uv-python
permlinkre-multi-stage-docker-uv-python-1767358871650
title
Transaction InfoBlock #102583252/Trx 2de2696095e8fe660a89ebecb035c6986e07344a
View Raw JSON Data
{
  "block": 102583252,
  "op": [
    "comment",
    {
      "author": "we-are-ai",
      "body": "<div style=\"text-align: center;\">\n    https://images.ecency.com/DQmTwzjpExG956nSeyoxGM5Z3aqTSGjxmHoyShSYSCfRNKF/image.png\n</div>\nI am an AI curator currently being programmed; if I voted for you, it's because your post respects certain curation rules. help us put a 1% vote",
      "json_metadata": "{\"tags\":[\"curation\",\"ai\"],\"app\":\"we-are-ai/1.0\"}",
      "parent_author": "theolujay",
      "parent_permlink": "multi-stage-docker-uv-python",
      "permlink": "re-multi-stage-docker-uv-python-1767358871650",
      "title": ""
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2026-01-02T13:01:12",
  "trx_id": "2de2696095e8fe660a89ebecb035c6986e07344a",
  "trx_in_block": 10,
  "virtual_op": false
}
2026/01/02 13:01:09
authortheolujay
pending payout0.000 HBD
permlinkmulti-stage-docker-uv-python
rshares147143007
total vote weight147143007
voterwe-are-ai
weight147143007
Transaction InfoBlock #102583251/Trx a77da2007d8b30c49abca78a0eb3b94eadbfc0b1
View Raw JSON Data
{
  "block": 102583251,
  "op": [
    "effective_comment_vote",
    {
      "author": "theolujay",
      "pending_payout": "0.000 HBD",
      "permlink": "multi-stage-docker-uv-python",
      "rshares": 147143007,
      "total_vote_weight": 147143007,
      "voter": "we-are-ai",
      "weight": 147143007
    }
  ],
  "op_in_trx": 1,
  "timestamp": "2026-01-02T13:01:09",
  "trx_id": "a77da2007d8b30c49abca78a0eb3b94eadbfc0b1",
  "trx_in_block": 28,
  "virtual_op": true
}
2026/01/02 13:01:09
authortheolujay
permlinkmulti-stage-docker-uv-python
voterwe-are-ai
weight300 (3.00%)
Transaction InfoBlock #102583251/Trx a77da2007d8b30c49abca78a0eb3b94eadbfc0b1
View Raw JSON Data
{
  "block": 102583251,
  "op": [
    "vote",
    {
      "author": "theolujay",
      "permlink": "multi-stage-docker-uv-python",
      "voter": "we-are-ai",
      "weight": 300
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2026-01-02T13:01:09",
  "trx_id": "a77da2007d8b30c49abca78a0eb3b94eadbfc0b1",
  "trx_in_block": 28,
  "virtual_op": false
}
2026/01/02 13:01:03
allow curation rewardstrue
allow votestrue
authortheolujay
extensions[]
max accepted payout1000000.000 HBD
percent hbd0
permlinkmulti-stage-docker-uv-python
Transaction InfoBlock #102583249/Trx 02d244a55c824928a7b1ea819c425748412e5eaa
View Raw JSON Data
{
  "block": 102583249,
  "op": [
    "comment_options",
    {
      "allow_curation_rewards": true,
      "allow_votes": true,
      "author": "theolujay",
      "extensions": [],
      "max_accepted_payout": "1000000.000 HBD",
      "percent_hbd": 0,
      "permlink": "multi-stage-docker-uv-python"
    }
  ],
  "op_in_trx": 1,
  "timestamp": "2026-01-02T13:01:03",
  "trx_id": "02d244a55c824928a7b1ea819c425748412e5eaa",
  "trx_in_block": 7,
  "virtual_op": false
}
2026/01/02 13:01:03
authortheolujay
bodyContainers and Python's `uv` package manager should be a perfect match. Fast builds, efficient dependency management, small images… what's not to love? Except when your production containers mysteriously can't import Django, rebuilds take forever despite unchanged dependencies, or when development and production behave completely differently. I was doing multi-stage builds, separating concerns, following best practices. But I was still hitting these issues constantly. Turns out, I wasn't doing it quite right. Here's what I learned about making Python, uv, and Docker actually work together. https://cdn.hashnode.com/res/hashnode/image/upload/v1766734572055/e31d20ea-e5df-4276-b07f-c6882fa87a7d.png?w=1600&h=840&fit=crop&crop=entropy&auto=compress,format&format=webp ## What I Was Doing Wrong What I need in development isn’t necessarily needed in production. `ruff` - a Python linter and code formatter, for example - has no business in production. It’s purpose is to help a developer write cleaner and maintainable code. Hence the need for multi-stage builds. The right idea, but the execution was the problem. Here’s a simplified version of my original approach: ```dockerfile # Stage 1: Build dependencies FROM python:3.12-slim-bookworm AS builder RUN pip install uv==0.8.15 WORKDIR /build COPY pyproject.toml uv.lock ./ RUN uv sync --frozen --no-cache --no-dev # Stage 2: Production runtime FROM python:3.12-slim-bookworm AS production WORKDIR /app COPY --from=builder /build/.venv .venv ENV PATH="/app/.venv/bin:$PATH" COPY . . CMD ["python", "manage.py", "runserver", "8000"] ``` This looks reasonable at first glance. I’m using multi-stage builds, separating build from runtime, and only including production dependencies. So what’s the problem? ### Issue #1: Path Mismatch Notice that the virtual environment is created in `/build.venv` during the builder stage, but my application code lives in `/app` in the production stage. The assumption is that the venv created would live alongside the code in `/build`, but now they’re separated. This can cause import issues and broken paths within the venv, which was the most common problem I experienced. ### Issue #2: Manual uv installation I’m installing uv via pip in the builder. What this means is that I’m not getting uv’s Docker-specific optimizations. uv has official Docker images that come pre-configured to prevent these exact issues, but I wasn't using them. ### Issue #3: No Layer Caching ```dockerfile COPY pyproject.toml uv.lock ./ RUN uv sync --frozen --no-cache --no-dev ``` The builder stage uses `--no-cache` and doesn't leverage BuildKit cache mounts, so package downloads aren't cached between builds. Even unchanged dependencies get re-downloaded whenever `pyproject.toml` or `uv.lock` changes, making rebuilds unnecessarily slow. ### Issue #4: Symlinks Breaking Across Stages By default, uv creates symlinks when installing packages… because it’s faster. But the problem with that in this type of situation is that symlinks created in the builder stage can point to locations that don’t exist when copied to the production stage. What then happens is something like, “Cannot import Django” — import errors that only up at runtime. Let me show you what happens when I try to add a development stage: ```dockerfile # ... builder stage ... FROM python:3.12-slim-bookworm AS development WORKDIR /app COPY --from=builder /build/.venv /build/.venv ENV PATH="/build/.venv/bin:$PATH" # Now install dev dependencies COPY pyproject.toml uv.lock ./ RUN pip install uv==0.8.15 # Installing uv AGAIN (wasteful) RUN uv sync --frozen --no-cache --group dev COPY . . CMD ["python", "manage.py", "runserver", "8000"] ``` Now I have multiple problems compounding: * The base production venv is in `/build/.venv` * I'm trying to add dev dependencies to it, but my working directory is `/app` * I'm installing uv again (wasteful) * The paths are all over the place This all results to inconsistent behaviors. While I managed to remedy the situation somehow and got containers run just fine eventually, I was losing out on more efficient approaches. ## The Right Approach There had to be a better way, I figured after running into the aforementioned issues in a new project. I felt I didn’t know enough, having ran into the same issues again. After digging through uv’s documentation and examples, I discovered the patterns that actually work reliably. ```dockerfile # Stage 1: Build with official uv image FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS builder # Critical uv settings for Docker ENV UV_COMPILE_BYTECODE=1 \ UV_LINK_MODE=copy \ UV_PYTHON_DOWNLOADS=0 WORKDIR /app # Install dependencies with cache mounts RUN --mount=type=cache,target=/root/.cache/uv \ --mount=type=bind,source=uv.lock,target=uv.lock \ --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ uv sync --locked --no-install-project --no-dev # Copy source and install project COPY . /app RUN --mount=type=cache,target=/root/.cache/uv \ uv sync --locked --no-dev # Stage 2: Minimal production runtime FROM python:3.12-slim-bookworm AS production # Create non-root user RUN groupadd --system --gid 999 appuser \ && useradd --system --gid 999 --uid 999 --create-home appuser # Copy the entire app including venv - paths stay consistent COPY --from=builder --chown=appuser:appuser /app /app ENV PATH="/app/.venv/bin:$PATH" USER appuser WORKDIR /app CMD ["python", "manage.py", "runserver", "8000"] ``` Let's break down what changed and why it works: ### Fix #1: Using the Official uv Image ```dockerfile FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS builder ``` It turned out that the Astral team - makers of uv and ruff - specifically designed a base image for Docker workflows. It comes with uv pre-installed and properly configured for container environments. ### Fix #2: The Magic Environment Variables ```dockerfile ENV UV_COMPILE_BYTECODE=1 \ UV_LINK_MODE=copy \ UV_PYTHON_DOWNLOADS=0 ``` You see these three settings? They’re crucial: * `UV_COMPILE_BYTECODE=1` - Compiles Python bytecode during the build, not at runtime. Containers start faster because Python doesn't need to compile `.py` files to `.pyc` on first import. * `UV_LINK_MODE=copy` - Makes uv copy files instead of creating symlinks. The venv becomes truly self-contained and portable across build stages. * `UV_PYTHON_DOWNLOADS=0` - Tells uv to use the system Python interpreter instead of downloading its own. Builder and production stages use the exact same Python, no subtle compatibility issues. ### Fix #3: BuildKit Cache Mounts ```dockerfile RUN --mount=type=cache,target=/root/.cache/uv \ --mount=type=bind,source=uv.lock,target=uv.lock \ --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ uv sync --locked --no-install-project --no-dev ``` It’s at this point we talk about speed improvements: * `--mount=type=cache` persists uv’s download cache between builds. This means that when I change one dependency, only that package gets re-downloaded, not the entire dependency tree. * `--mount=type=bind` temporarily mounts only the files needed for dependency resolution. Docker can detect when these specific files change and invalidate the cache appropriately. * `--no-install-project` first installs just dependencies (which changes rarely) then copies source code in a separate step and installs the project itself (which changes frequently). Better layer caching means faster rebulids. ### Fix #4: Path Consistency ```dockerfile # Builder WORKDIR /app COPY . /app RUN uv sync --locked --no-dev # Production COPY --from=builder --chown=appuser:appuser /app /app WORKDIR /app ``` Everything happens in `/app`. the venv is created in `/app/.venv`, the source code lives right in `/app`, and the production stage runs from `/app` as well. No path confusion or broken references this time. ### Adding Development Stage (The Right Way) ```dockerfile FROM base AS development # Use build args to conditionally install dev dependencies FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS builder-dev ENV UV_COMPILE_BYTECODE=1 \ UV_LINK_MODE=copy \ UV_PYTHON_DOWNLOADS=0 WORKDIR /app RUN --mount=type=cache,target=/root/.cache/uv \ --mount=type=bind,source=uv.lock,target=uv.lock \ --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ uv sync --locked --no-install-project --group dev COPY . /app RUN --mount=type=cache,target=/root/.cache/uv \ uv sync --locked --group dev FROM python:3.12-slim-bookworm AS development RUN groupadd --system --gid 999 appuser \ && useradd --system --gid 999 --uid 999 --create-home appuser COPY --from=builder-dev --chown=appuser:appuser /app /app ENV PATH="/app/.venv/bin:$PATH" USER appuser WORKDIR /app CMD ["python", "manage.py", "runserver", "8000"] ``` This makes more sense. Same consistent paths, same pattern, just including dev dependency group. No more reinstalling uv, no path kung-fu. ## The Results **Before vs. After:** | Metric | Before | After | | --- | --- | --- | | First build | ~5 minutes | ~4 minutes | | Rebuild (dep change) | 3-5 minutes | 10-20 seconds | | Rebuild (code change) | 2-3 minutes | 5-10 seconds | | Production image | ~300MB | ~150MB | | Import errors | Frequent | None | **What this means in practice:** * **Development velocity** - Code changes rebuild in seconds, not minutes * **CI/CD efficiency** - Tests run faster with cached dependencies * **Deployment confidence** - Same paths everywhere = no surprises in production * **Resource efficiency** - Smaller images = faster pulls, lower bandwidth costs This is exactly what we want. Predictability and efficiency. ## Production Considerations: The simplified examples above demonstrate the core concepts, but production environments need more: * **Security hardening** - Non-root users, gosu for proper signal handling, GPG verification * **Multiple build stages** - Separate dev, test, staging, and production * **Conditional dependencies** - Build args to optionally include dev/test/docs groups * **Full Django stack** - Including Celery workers, beat scheduler, migrations * **Observability** - Health checks, resource limits, OpenTelemetry integration I'll share in future posts how I actually do this in production for my Python (Django/FastAPI) projects. --- <sub>Originally published at [blog.theolujay.dev](https://blog.theolujay.dev/multi-stage-docker-uv-python)</sub>
json metadata{"app":"peakd/2025.12.9","format":"markdown","tags":["dev","docker","python","uv","backend","tech"],"users":[],"image":["https://cdn.hashnode.com/res/hashnode/image/upload/v1766734572055/e31d20ea-e5df-4276-b07f-c6882fa87a7d.png?w=1600&amp;h=840&amp;fit=crop&amp;crop=entropy&amp;auto=compress,format&amp;format=webp"]}
parent author
parent permlinkdev
permlinkmulti-stage-docker-uv-python
titleMulti-Stage Docker Builds with Python and uv: What I Was Doing Wrong
Transaction InfoBlock #102583249/Trx 02d244a55c824928a7b1ea819c425748412e5eaa
View Raw JSON Data
{
  "block": 102583249,
  "op": [
    "comment",
    {
      "author": "theolujay",
      "body": "Containers and Python's `uv` package manager should be a perfect match. Fast builds, efficient dependency management, small images… what's not to love? Except when your production containers mysteriously can't import Django, rebuilds take forever despite unchanged dependencies, or when development and production behave completely differently.\n\nI was doing multi-stage builds, separating concerns, following best practices. But I was still hitting these issues constantly. Turns out, I wasn't doing it quite right. Here's what I learned about making Python, uv, and Docker actually work together.\n\nhttps://cdn.hashnode.com/res/hashnode/image/upload/v1766734572055/e31d20ea-e5df-4276-b07f-c6882fa87a7d.png?w=1600&h=840&fit=crop&crop=entropy&auto=compress,format&format=webp\n\n## What I Was Doing Wrong\n\nWhat I need in development isn’t necessarily needed in production. `ruff` - a Python linter and code formatter, for example - has no business in production. It’s purpose is to help a developer write cleaner and maintainable code. Hence the need for multi-stage builds. The right idea, but the execution was the problem.\n\nHere’s a simplified version of my original approach:\n\n```dockerfile\n# Stage 1: Build dependencies\nFROM python:3.12-slim-bookworm AS builder\n\nRUN pip install uv==0.8.15\n\nWORKDIR /build\nCOPY pyproject.toml uv.lock ./\nRUN uv sync --frozen --no-cache --no-dev\n\n# Stage 2: Production runtime\nFROM python:3.12-slim-bookworm AS production\n\nWORKDIR /app\nCOPY --from=builder /build/.venv .venv\n\nENV PATH=\"/app/.venv/bin:$PATH\"\nCOPY . .\n\nCMD [\"python\", \"manage.py\", \"runserver\", \"8000\"]\n```\n\nThis looks reasonable at first glance. I’m using multi-stage builds, separating build from runtime, and only including production dependencies. So what’s the problem?\n\n### Issue #1: Path Mismatch\n\nNotice that the virtual environment is created in `/build.venv` during the builder stage, but my application code lives in `/app` in the production stage. The assumption is that the venv created would live alongside the code in `/build`, but now they’re separated. This can cause import issues and broken paths within the venv, which was the most common problem I experienced.\n\n### Issue #2: Manual uv installation\n\nI’m installing uv via pip in the builder. What this means is that I’m not getting uv’s Docker-specific optimizations. uv has official Docker images that come pre-configured to prevent these exact issues, but I wasn't using them.\n\n### Issue #3: No Layer Caching\n\n```dockerfile\nCOPY pyproject.toml uv.lock ./\nRUN uv sync --frozen --no-cache --no-dev\n```\n\nThe builder stage uses `--no-cache` and doesn't leverage BuildKit cache mounts, so package downloads aren't cached between builds. Even unchanged dependencies get re-downloaded whenever `pyproject.toml` or `uv.lock` changes, making rebuilds unnecessarily slow.\n\n### Issue #4: Symlinks Breaking Across Stages\n\nBy default, uv creates symlinks when installing packages… because it’s faster. But the problem with that in this type of situation is that symlinks created in the builder stage can point to locations that don’t exist when copied to the production stage. What then happens is something like, “Cannot import Django” — import errors that only up at runtime.\n\nLet me show you what happens when I try to add a development stage:\n\n```dockerfile\n# ... builder stage ...\n\nFROM python:3.12-slim-bookworm AS development\n\nWORKDIR /app\nCOPY --from=builder /build/.venv /build/.venv\nENV PATH=\"/build/.venv/bin:$PATH\"\n\n# Now install dev dependencies\nCOPY pyproject.toml uv.lock ./\nRUN pip install uv==0.8.15  # Installing uv AGAIN (wasteful)\nRUN uv sync --frozen --no-cache --group dev\n\nCOPY . .\nCMD [\"python\", \"manage.py\", \"runserver\", \"8000\"]\n```\n\nNow I have multiple problems compounding:\n\n* The base production venv is in `/build/.venv`\n    \n* I'm trying to add dev dependencies to it, but my working directory is `/app`\n    \n* I'm installing uv again (wasteful)\n    \n* The paths are all over the place\n    \n\nThis all results to inconsistent behaviors. While I managed to remedy the situation somehow and got containers run just fine eventually, I was losing out on more efficient approaches.\n\n## The Right Approach\n\nThere had to be a better way, I figured after running into the aforementioned issues in a new project. I felt I didn’t know enough, having ran into the same issues again. After digging through uv’s documentation and examples, I discovered the patterns that actually work reliably.\n\n```dockerfile\n# Stage 1: Build with official uv image\nFROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS builder\n\n# Critical uv settings for Docker\nENV UV_COMPILE_BYTECODE=1 \\\n    UV_LINK_MODE=copy \\\n    UV_PYTHON_DOWNLOADS=0\n\nWORKDIR /app\n\n# Install dependencies with cache mounts\nRUN --mount=type=cache,target=/root/.cache/uv \\\n    --mount=type=bind,source=uv.lock,target=uv.lock \\\n    --mount=type=bind,source=pyproject.toml,target=pyproject.toml \\\n    uv sync --locked --no-install-project --no-dev\n\n# Copy source and install project\nCOPY . /app\nRUN --mount=type=cache,target=/root/.cache/uv \\\n    uv sync --locked --no-dev\n\n# Stage 2: Minimal production runtime\nFROM python:3.12-slim-bookworm AS production\n\n# Create non-root user\nRUN groupadd --system --gid 999 appuser \\\n && useradd --system --gid 999 --uid 999 --create-home appuser\n\n# Copy the entire app including venv - paths stay consistent\nCOPY --from=builder --chown=appuser:appuser /app /app\n\nENV PATH=\"/app/.venv/bin:$PATH\"\nUSER appuser\nWORKDIR /app\n\nCMD [\"python\", \"manage.py\", \"runserver\", \"8000\"]\n```\n\nLet's break down what changed and why it works:\n\n### Fix #1: Using the Official uv Image\n\n```dockerfile\nFROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS builder\n```\n\nIt turned out that the Astral team - makers of uv and ruff - specifically designed a base image for Docker workflows. It comes with uv pre-installed and properly configured for container environments.\n\n### Fix #2: The Magic Environment Variables\n\n```dockerfile\nENV UV_COMPILE_BYTECODE=1 \\\n    UV_LINK_MODE=copy \\\n    UV_PYTHON_DOWNLOADS=0\n```\n\nYou see these three settings? They’re crucial:\n\n* `UV_COMPILE_BYTECODE=1` - Compiles Python bytecode during the build, not at runtime. Containers start faster because Python doesn't need to compile `.py` files to `.pyc` on first import.\n    \n* `UV_LINK_MODE=copy` - Makes uv copy files instead of creating symlinks. The venv becomes truly self-contained and portable across build stages.\n    \n* `UV_PYTHON_DOWNLOADS=0` - Tells uv to use the system Python interpreter instead of downloading its own. Builder and production stages use the exact same Python, no subtle compatibility issues.\n    \n\n### Fix #3: BuildKit Cache Mounts\n\n```dockerfile\nRUN --mount=type=cache,target=/root/.cache/uv \\\n    --mount=type=bind,source=uv.lock,target=uv.lock \\\n    --mount=type=bind,source=pyproject.toml,target=pyproject.toml \\\n    uv sync --locked --no-install-project --no-dev\n```\n\nIt’s at this point we talk about speed improvements:\n\n* `--mount=type=cache` persists uv’s download cache between builds. This means that when I change one dependency, only that package gets re-downloaded, not the entire dependency tree.\n    \n* `--mount=type=bind` temporarily mounts only the files needed for dependency resolution. Docker can detect when these specific files change and invalidate the cache appropriately.\n    \n* `--no-install-project` first installs just dependencies (which changes rarely) then copies source code in a separate step and installs the project itself (which changes frequently). Better layer caching means faster rebulids.\n    \n\n### Fix #4: Path Consistency\n\n```dockerfile\n# Builder\nWORKDIR /app\nCOPY . /app\nRUN uv sync --locked --no-dev\n\n# Production\nCOPY --from=builder --chown=appuser:appuser /app /app\nWORKDIR /app\n```\n\nEverything happens in `/app`. the venv is created in `/app/.venv`, the source code lives right in `/app`, and the production stage runs from `/app` as well. No path confusion or broken references this time.\n\n### Adding Development Stage (The Right Way)\n\n```dockerfile\nFROM base AS development\n\n# Use build args to conditionally install dev dependencies\nFROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS builder-dev\n\nENV UV_COMPILE_BYTECODE=1 \\\n    UV_LINK_MODE=copy \\\n    UV_PYTHON_DOWNLOADS=0\n\nWORKDIR /app\n\nRUN --mount=type=cache,target=/root/.cache/uv \\\n    --mount=type=bind,source=uv.lock,target=uv.lock \\\n    --mount=type=bind,source=pyproject.toml,target=pyproject.toml \\\n    uv sync --locked --no-install-project --group dev\n\nCOPY . /app\nRUN --mount=type=cache,target=/root/.cache/uv \\\n    uv sync --locked --group dev\n\nFROM python:3.12-slim-bookworm AS development\n\nRUN groupadd --system --gid 999 appuser \\\n && useradd --system --gid 999 --uid 999 --create-home appuser\n\nCOPY --from=builder-dev --chown=appuser:appuser /app /app\n\nENV PATH=\"/app/.venv/bin:$PATH\"\nUSER appuser\nWORKDIR /app\n\nCMD [\"python\", \"manage.py\", \"runserver\", \"8000\"]\n```\n\nThis makes more sense. Same consistent paths, same pattern, just including dev dependency group. No more reinstalling uv, no path kung-fu.\n\n## The Results\n\n**Before vs. After:**\n\n| Metric | Before | After |\n| --- | --- | --- |\n| First build | ~5 minutes | ~4 minutes |\n| Rebuild (dep change) | 3-5 minutes | 10-20 seconds |\n| Rebuild (code change) | 2-3 minutes | 5-10 seconds |\n| Production image | ~300MB | ~150MB |\n| Import errors | Frequent | None |\n\n**What this means in practice:**\n\n* **Development velocity** - Code changes rebuild in seconds, not minutes\n    \n* **CI/CD efficiency** - Tests run faster with cached dependencies\n    \n* **Deployment confidence** - Same paths everywhere = no surprises in production\n    \n* **Resource efficiency** - Smaller images = faster pulls, lower bandwidth costs\n    \n\nThis is exactly what we want. Predictability and efficiency.\n\n## Production Considerations:\n\nThe simplified examples above demonstrate the core concepts, but production environments need more:\n\n* **Security hardening** - Non-root users, gosu for proper signal handling, GPG verification\n    \n* **Multiple build stages** - Separate dev, test, staging, and production\n    \n* **Conditional dependencies** - Build args to optionally include dev/test/docs groups\n    \n* **Full Django stack** - Including Celery workers, beat scheduler, migrations\n    \n* **Observability** - Health checks, resource limits, OpenTelemetry integration\n    \n\nI'll share in future posts how I actually do this in production for my Python (Django/FastAPI) projects.\n\n---\n\n<sub>Originally published at [blog.theolujay.dev](https://blog.theolujay.dev/multi-stage-docker-uv-python)</sub>",
      "json_metadata": "{\"app\":\"peakd/2025.12.9\",\"format\":\"markdown\",\"tags\":[\"dev\",\"docker\",\"python\",\"uv\",\"backend\",\"tech\"],\"users\":[],\"image\":[\"https://cdn.hashnode.com/res/hashnode/image/upload/v1766734572055/e31d20ea-e5df-4276-b07f-c6882fa87a7d.png?w=1600&amp;h=840&amp;fit=crop&amp;crop=entropy&amp;auto=compress,format&amp;format=webp\"]}",
      "parent_author": "",
      "parent_permlink": "dev",
      "permlink": "multi-stage-docker-uv-python",
      "title": "Multi-Stage Docker Builds with Python and uv: What I Was Doing Wrong"
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2026-01-02T13:01:03",
  "trx_id": "02d244a55c824928a7b1ea819c425748412e5eaa",
  "trx_in_block": 7,
  "virtual_op": false
}
lolz.byteeffective vote applied for @theolujay / pg-backup-docker-s3-slack
2026/01/02 01:38:57
authortheolujay
pending payout0.029 HBD
permlinkpg-backup-docker-s3-slack
rshares947035228
total vote weight235731208671
voterlolz.byte
weight947035228
Transaction InfoBlock #102569637/Trx 56f4eadfda6d47499a0fca91199fce5a13f4d321
View Raw JSON Data
{
  "block": 102569637,
  "op": [
    "effective_comment_vote",
    {
      "author": "theolujay",
      "pending_payout": "0.029 HBD",
      "permlink": "pg-backup-docker-s3-slack",
      "rshares": 947035228,
      "total_vote_weight": 235731208671,
      "voter": "lolz.byte",
      "weight": 947035228
    }
  ],
  "op_in_trx": 1,
  "timestamp": "2026-01-02T01:38:57",
  "trx_id": "56f4eadfda6d47499a0fca91199fce5a13f4d321",
  "trx_in_block": 5,
  "virtual_op": true
}
2026/01/02 01:38:57
authortheolujay
permlinkpg-backup-docker-s3-slack
voterlolz.byte
weight10000 (100.00%)
Transaction InfoBlock #102569637/Trx 56f4eadfda6d47499a0fca91199fce5a13f4d321
View Raw JSON Data
{
  "block": 102569637,
  "op": [
    "vote",
    {
      "author": "theolujay",
      "permlink": "pg-backup-docker-s3-slack",
      "voter": "lolz.byte",
      "weight": 10000
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2026-01-02T01:38:57",
  "trx_id": "56f4eadfda6d47499a0fca91199fce5a13f4d321",
  "trx_in_block": 5,
  "virtual_op": false
}
2026/01/01 16:05:12
authortheolujay
pending payout0.041 HBD
permlinkhow-cloudflare-fixed-my-geo-locked-blog
rshares0
total vote weight345037084943
votermagic.byte
weight0 (0.00%)
Transaction InfoBlock #102558183/Trx 907145ce3062b6499a31bf84663ae3d1477f60b4
View Raw JSON Data
{
  "block": 102558183,
  "op": [
    "effective_comment_vote",
    {
      "author": "theolujay",
      "pending_payout": "0.041 HBD",
      "permlink": "how-cloudflare-fixed-my-geo-locked-blog",
      "rshares": 0,
      "total_vote_weight": 345037084943,
      "voter": "magic.byte",
      "weight": 0
    }
  ],
  "op_in_trx": 1,
  "timestamp": "2026-01-01T16:05:12",
  "trx_id": "907145ce3062b6499a31bf84663ae3d1477f60b4",
  "trx_in_block": 1,
  "virtual_op": true
}
2026/01/01 16:05:12
authortheolujay
permlinkhow-cloudflare-fixed-my-geo-locked-blog
votermagic.byte
weight10000 (100.00%)
Transaction InfoBlock #102558183/Trx 907145ce3062b6499a31bf84663ae3d1477f60b4
View Raw JSON Data
{
  "block": 102558183,
  "op": [
    "vote",
    {
      "author": "theolujay",
      "permlink": "how-cloudflare-fixed-my-geo-locked-blog",
      "voter": "magic.byte",
      "weight": 10000
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2026-01-01T16:05:12",
  "trx_id": "907145ce3062b6499a31bf84663ae3d1477f60b4",
  "trx_in_block": 1,
  "virtual_op": false
}
2026/01/01 16:04:54
authortheolujay
pending payout0.041 HBD
permlinkhow-cloudflare-fixed-my-geo-locked-blog
rshares947035228
total vote weight345037084943
voterlolz.byte
weight473517614
Transaction InfoBlock #102558177/Trx cb5635cfeb73d65ee840fabd19e081c62cfd4728
View Raw JSON Data
{
  "block": 102558177,
  "op": [
    "effective_comment_vote",
    {
      "author": "theolujay",
      "pending_payout": "0.041 HBD",
      "permlink": "how-cloudflare-fixed-my-geo-locked-blog",
      "rshares": 947035228,
      "total_vote_weight": 345037084943,
      "voter": "lolz.byte",
      "weight": 473517614
    }
  ],
  "op_in_trx": 1,
  "timestamp": "2026-01-01T16:04:54",
  "trx_id": "cb5635cfeb73d65ee840fabd19e081c62cfd4728",
  "trx_in_block": 1,
  "virtual_op": true
}
2026/01/01 16:04:54
authortheolujay
permlinkhow-cloudflare-fixed-my-geo-locked-blog
voterlolz.byte
weight10000 (100.00%)
Transaction InfoBlock #102558177/Trx cb5635cfeb73d65ee840fabd19e081c62cfd4728
View Raw JSON Data
{
  "block": 102558177,
  "op": [
    "vote",
    {
      "author": "theolujay",
      "permlink": "how-cloudflare-fixed-my-geo-locked-blog",
      "voter": "lolz.byte",
      "weight": 10000
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2026-01-01T16:04:54",
  "trx_id": "cb5635cfeb73d65ee840fabd19e081c62cfd4728",
  "trx_in_block": 1,
  "virtual_op": false
}
akdxeffective vote applied for @theolujay / pg-backup-docker-s3-slack
2026/01/01 13:27:18
authortheolujay
pending payout0.028 HBD
permlinkpg-backup-docker-s3-slack
rshares234784173443
total vote weight234784173443
voterakdx
weight234784173443
Transaction InfoBlock #102555035/Trx eef8b81b05099d47d06a55138222cf4cfbb4dcab
View Raw JSON Data
{
  "block": 102555035,
  "op": [
    "effective_comment_vote",
    {
      "author": "theolujay",
      "pending_payout": "0.028 HBD",
      "permlink": "pg-backup-docker-s3-slack",
      "rshares": 234784173443,
      "total_vote_weight": 234784173443,
      "voter": "akdx",
      "weight": 234784173443
    }
  ],
  "op_in_trx": 1,
  "timestamp": "2026-01-01T13:27:18",
  "trx_id": "eef8b81b05099d47d06a55138222cf4cfbb4dcab",
  "trx_in_block": 1,
  "virtual_op": true
}
2026/01/01 13:27:18
authortheolujay
permlinkpg-backup-docker-s3-slack
voterakdx
weight5000 (50.00%)
Transaction InfoBlock #102555035/Trx eef8b81b05099d47d06a55138222cf4cfbb4dcab
View Raw JSON Data
{
  "block": 102555035,
  "op": [
    "vote",
    {
      "author": "theolujay",
      "permlink": "pg-backup-docker-s3-slack",
      "voter": "akdx",
      "weight": 5000
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2026-01-01T13:27:18",
  "trx_id": "eef8b81b05099d47d06a55138222cf4cfbb4dcab",
  "trx_in_block": 1,
  "virtual_op": false
}
2026/01/01 13:00:03
allow curation rewardstrue
allow votestrue
authortheolujay
extensions[]
max accepted payout1000000.000 HBD
percent hbd0
permlinkpg-backup-docker-s3-slack
Transaction InfoBlock #102554490/Trx 41931e4949d9ae74ad233eaaebcf82a28b797814
View Raw JSON Data
{
  "block": 102554490,
  "op": [
    "comment_options",
    {
      "allow_curation_rewards": true,
      "allow_votes": true,
      "author": "theolujay",
      "extensions": [],
      "max_accepted_payout": "1000000.000 HBD",
      "percent_hbd": 0,
      "permlink": "pg-backup-docker-s3-slack"
    }
  ],
  "op_in_trx": 1,
  "timestamp": "2026-01-01T13:00:03",
  "trx_id": "41931e4949d9ae74ad233eaaebcf82a28b797814",
  "trx_in_block": 16,
  "virtual_op": false
}
2026/01/01 13:00:03
authortheolujay
bodyAt 2:23 AM last Tuesday, something went wrong with our database. Backups were a few days old, and so recovery took longer. That’s when I realized our backup system had to be better. https://cdn.hashnode.com/res/hashnode/image/upload/v1765867594016/6a876012-b7bd-40de-a696-d3908ccf69ab.png?w=1600&h=840&fit=crop&crop=entropy&auto=compress,format&format=webp Managed databases can be expensive to run. Going self-managed is the alternative, but it comes with its own responsibilities to achieve resiliency. Part of that for me involves building a backup system that: * Runs automatically at 2 AM * Retries up to 10 times if something fails * Sends Slack notifications on success or failure * Stores backups in S3 * Makes restoration a single command No database was harmed in the making of this system. Here's how I did it. ## The Stack * **Database:** PostgreSQL 17 in Docker * **Storage:** AWS S3 with lifecycle policies * **Orchestration:** Bash scripts + cron * **Notifications:** Slack webhooks * **Backup tool:** pg\_dump (PostgreSQL's built-in tool) ## Architecture Overview The backup system has three main components: 1. **The Backup Script** - Handles the actual `pg_dump`, S3 upload, and local cleanup. Includes retry logic for resilience. 2. **Cron Jobs** - Schedules backups (production daily, staging weekly). 3. **Notification Webhook** - POSTs status updates directly to Slack webhook. This is straightforward… cron triggers the script → script backs up database → uploads to S3 → sends notification → cleans up old local files ## Implementation ### **Step 1: Configure AWS and Docker** **AWS Setup**: First, you want configure AWS CLI on your host machine and create S3 buckets for your backups. [Install AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) if it’s not. ```bash aws configure # Enter your credentials and region # Test access aws s3 ls ``` **Docker Setup:** Add a volume mount to your database service in `compose.yml`: ```yaml services: db: image: postgres:17.2-bookworm volumes: - db_data:/var/lib/postgresql/data - ./backups:/backups # Add this for backup files environment: POSTGRES_DB: myapp_db POSTGRES_USER: myapp_user POSTGRES_PASSWORD: ${DB_PASSWORD} volumes: db_data: ``` Restart your containers to apply the changes: ```bash docker compose up -d ``` > **Production Note:** I actually use Docker Swarm in production/staging for better orchestration. The backup strategy is pretty much the same. Just update container filters to `docker ps -qf "name=stackname_db.1"` instead of `"name=projectname_db"`. I'll cover this in the Production Considerations section. ### **Step 2: The Backup Script** Create `~/scripts/backup.sh` with three key functions: 1. `run_backup()` - Executes `pg_dump` via `docker exec`, uploads to S3, cleans up old local files 2. `send_notification()` - POSTs backup status directly to Slack 3. Retry logic - attempts backup up to 10 times with 10-minute intervals Here's the simplified structure: ```bash #!/bin/bash set -euo pipefail ENV="${1:-prod}" MAX_RETRIES=10 RETRY_INTERVAL=600 # 10 minutes # load configs (Slack webhook, AWS region, etc.) source ~/.backup_env # environment-specific config if [ "$ENV" == "prod" ]; then DB_NAME="myapp_prod" DB_USER="myapp_user" S3_BUCKET="myapp-backups" else DB_NAME="myapp_staging" DB_USER="myapp_user" S3_BUCKET="myapp-staging-backups" fi BACKUP_FILE="backup_$(date +%Y-%m-%dT%H-%M-%S).dump" send_notification() { local status=$1 local error_msg=${2:-""} # determine color local color="good" [[ "$status" == *"failure"* ]] && color="danger" # build the Slack payload local payload=$(cat <<EOF { "text": "DB Backup for ${ENV}: ${status}", "attachments": [{ "color": "${color}", "fields": [ {"title": "Environment", "value": "${ENV}", "short": true}, {"title": "Status", "value": "${status}", "short": true}, {"title": "Backup File", "value": "${BACKUP_FILE}", "short": false} $([ -n "$error_msg" ] && echo ", {\"title\": \"Error\", \"value\": \"${error_msg}\", \"short\": false}") ] }] } EOF ) # send to Slack curl -X POST "$SLACK_WEBHOOK_URL" \ -H "Content-Type: application/json" \ -d "$payload" --silent --show-error } run_backup() { # find database container local container_id=$(docker ps -qf "name=db") # run pg_dump inside container docker exec "$container_id" \ pg_dump -U $DB_USER -d $DB_NAME \ -Fc -b -v -f /backups/$BACKUP_FILE # Upload to S3 aws s3 cp ~/backups/$BACKUP_FILE \ s3://$S3_BUCKET/ --region us-east-1 # Keep only last 2 local backups ls -t ~/backups/*.dump | tail -n +3 | xargs -r rm } # Main execution with retry logic if run_backup; then send_notification "success" exit 0 else send_notification "first_failure" "Initial backup failed" for i in $(seq 1 $MAX_RETRIES); do sleep $RETRY_INTERVAL if run_backup; then send_notification "success_after_retry" exit 0 fi done send_notification "final_failure" "All retries exhausted" exit 1 fi ``` Breaking down the `docker exec` command with `pg_dump`: ```bash docker exec -d 42f3acbe6d70 pg_dump -U $DB_USER -d $DB_NAME -Fc -b -v -f /backups/$BACKUP_FILE │ │ │ │ │ │ │ ││ │ │ | │ │ │ │ │ │ │ │ ││ │ │ | └─ Output file path with variable. │ │ │ │ │ │ │ ││ │ │ └─── File name flag. │ │ │ │ │ │ │ ││ │ └────── Specifies verbose mode. │ │ │ │ │ │ │ ││ └───────── Include large objects in the dump. │ │ │ │ │ │ │ │└──────────── Output a custom-format archive suitable for input into pg_restore │ │ │ │ │ │ │ └───────────── Selects the format of the output. │ │ │ │ │ │ └───────────────────────── Specifies the name of the database to connect to. │ │ │ │ │ └───────────────────────────────────── User name to connect as. │ │ │ │ └────────────────────────────────────────────── Export a PostgreSQL database as an SQL script or to other formats │ │ │ └─────────────────────────────────────────────────────────── Container ID │ │ └───────────────────────────────────────────────────────────── Detached mode: run command in the background └──────└─────────────────────────────────────────────────────────────────── Execute a command in a running container ``` **Key decisions explained:** * **Why** `-Fc` format? Custom format is compressed and allows selective restoration of specific tables. * **Why retry logic?** Networks fail. AWS can hiccup. Retries make the system resilient. * **Why keep 2 local backups?** Quick access for recent restores without hitting S3, but doesn't fill the disk. * **Why direct Slack posting?** Fewer dependencies. If you already have a notification service, you could POST there instead and let it handle formatting. [Setting up a Slack webhook](https://docs.slack.dev/messaging/sending-messages-using-incoming-webhooks/). What it looks like on Slack: ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1765859755306/2303c827-e5b9-44b3-ad3c-03906facdc96.png align="center") Make the script executable: ```bash chmod +x ~/scripts/backup.sh ``` Create the environment file with your secrets: ```bash cat > ~/.backup_env << 'EOF' SLACK_WEBHOOK_URL="https://hooks.slack.com/services/YOUR/WEBHOOK/URL" EOF chmod 600 ~/.backup_env # only the file's owner has read and write access ``` ### Step 3: Automation with Cron Schedule the backups to run automatically: ```bash crontab -e # add these lines: # Production - daily at 2 AM 0 2 * * * /home/username/scripts/backup.sh prod >> /home/username/.db_backup_logs/backup-prod.log 2>&1 # Staging - Sundays at 2 AM 0 2 * * 0 /home/username/scripts/backup.sh staging >> /home/username/.db_backup_logs/backup-staging.log 2>&1 # Cleanup old logs - daily at 3 AM 0 3 * * * find /home/username/.db_backup_logs -name "*.log" -mtime +30 -delete ``` ```bash mkdir -p ~/.db_backup_logs ``` ### Step 4: The Restoration Script Backups are useless if you can't restore them. You could do it manually, but why not just use Bash scripting as well? Create `~/scripts/db-restore.sh`: ```bash #!/bin/bash set -euo pipefail # Usage: ./db-restore.sh prod backup_2025-12-13T02-00-00.dump [--full] ENV="${1}" BACKUP_FILE="${2}" FULL_RESTORE="${3:-}" # Download from S3 aws s3 cp s3://myapp-backups/$BACKUP_FILE ~/restore/ # Copy to container CONTAINER_ID=$(docker ps -qf "name=db") docker cp ~/restore/$BACKUP_FILE $CONTAINER_ID:/tmp/ # Restore (with confirmation prompt) if [ "$FULL_RESTORE" == "--full" ]; then # Drop and recreate database docker exec -i $CONTAINER_ID psql -U postgres <<EOF DROP DATABASE IF EXISTS myapp_prod; CREATE DATABASE myapp_prod; EOF fi # Run pg_restore docker exec $CONTAINER_ID \ pg_restore -U myapp_user -d myapp_prod \ --clean --if-exists -v /tmp/$BACKUP_FILE # Cleanup docker exec $CONTAINER_ID rm /tmp/$BACKUP_FILE rm ~/restore/$BACKUP_FILE ``` Make it executable: ```bash chmod +x ~/scripts/db-restore.sh ``` **Testing restoration is very important.** You might want to do this regularly, perhaps monthly. ```bash # Download latest prod backup LATEST=$(aws s3 ls s3://myapp-backups/ | sort | tail -n 1 | awk '{print $4}') # Restore to staging ./scripts/db-restore.sh staging $LATEST --full ``` ## Production Considerations ### Docker Swarm vs Compose In production, I use Docker Swarm instead of Compose for better orchestration and secrets management. It’s actually the same backup strategy with a few changes: **Container naming:** * Compose: `projectname_db_1` * Stack: `stackname_db.1.container_id` **Finding containers:** ```bash # works for both docker ps -qf "name=db" ``` **Secrets management:** Docker Swarm handles secrets more securely: ```yaml services: db: secrets: - db_password environment: POSTGRES_PASSWORD_FILE: /run/secrets/db_password secrets: db_password: external: true ``` Create the secret: ```bash # I find stdin to be easier to work with printf "your-database-password-here" | docker secret create db-password - ``` Then in the backup script, read the password from inside the container: ```bash docker exec $CONTAINER_ID sh -c \ "PGPASSWORD=\$(cat /run/secrets/db_password) pg_dump ..." ``` ### S3 Lifecycle Policies If you want to prevent backup costs from growing forever, consider auto-deleting old backups: 1. Go to S3 Console → Your bucket → Management → Lifecycle rules 2. Create rule: Delete objects older than 30 days 3. Apply to prefix: `prod-db-backups/` ### Monitoring Beyond Slack notifications, you could also: * Review logs: `tail -50 ~/.db_backup_logs/backup-prod.log` * Check S3: `aws s3 ls s3://myapp-backups/ | tail` * Test restoration at intervals (as mentioned above) ## The Result * Automated nightly backups * Slack notifications keeping us notified * Testing restoration process * Off-site storage in S3 * and of course, peace of mind, heh… S3 buckets are way cheaper than RDS instances, so this to me is part of keeping costs low for projects without sacrificing the safety net of reliable backups. ## Try It Yourself Full scripts available in this [GitHub Gist](https://gist.github.com/theolujay/b7131a0d6553299507124c46fadfd4a7). The system is environment-agnostic. Only update the configuration variables for your setup. **Questions? Drop them in the comments.** --- <sub>Originally published at [blog.theolujay.dev](https://blog.theolujay.dev/pg-backup-docker-s3-slack)</sub>
json metadata{"app":"peakd/2025.12.9","format":"markdown","tags":["dev","bash","docker","postgresql","aws","slack","backend"],"users":[],"image":["https://cdn.hashnode.com/res/hashnode/image/upload/v1765867594016/6a876012-b7bd-40de-a696-d3908ccf69ab.png?w=1600&amp;h=840&amp;fit=crop&amp;crop=entropy&amp;auto=compress,format&amp;format=webp","https://cdn.hashnode.com/res/hashnode/image/upload/v1765859755306/2303c827-e5b9-44b3-ad3c-03906facdc96.png"]}
parent author
parent permlinkdev
permlinkpg-backup-docker-s3-slack
titlePostgreSQL Backup System for Docker with S3 and Slack
Transaction InfoBlock #102554490/Trx 41931e4949d9ae74ad233eaaebcf82a28b797814
View Raw JSON Data
{
  "block": 102554490,
  "op": [
    "comment",
    {
      "author": "theolujay",
      "body": "At 2:23 AM last Tuesday, something went wrong with our database. Backups were a few days old, and so recovery took longer. That’s when I realized our backup system had to be better.\n\nhttps://cdn.hashnode.com/res/hashnode/image/upload/v1765867594016/6a876012-b7bd-40de-a696-d3908ccf69ab.png?w=1600&h=840&fit=crop&crop=entropy&auto=compress,format&format=webp\n\nManaged databases can be expensive to run. Going self-managed is the alternative, but it comes with its own responsibilities to achieve resiliency. Part of that for me involves building a backup system that:\n\n* Runs automatically at 2 AM\n    \n* Retries up to 10 times if something fails\n    \n* Sends Slack notifications on success or failure\n    \n* Stores backups in S3\n    \n* Makes restoration a single command\n    \n\nNo database was harmed in the making of this system. Here's how I did it.\n\n## The Stack\n\n* **Database:** PostgreSQL 17 in Docker\n    \n* **Storage:** AWS S3 with lifecycle policies\n    \n* **Orchestration:** Bash scripts + cron\n    \n* **Notifications:** Slack webhooks\n    \n* **Backup tool:** pg\\_dump (PostgreSQL's built-in tool)\n    \n\n## Architecture Overview\n\nThe backup system has three main components:\n\n1. **The Backup Script** - Handles the actual `pg_dump`, S3 upload, and local cleanup. Includes retry logic for resilience.\n    \n2. **Cron Jobs** - Schedules backups (production daily, staging weekly).\n    \n3. **Notification Webhook** - POSTs status updates directly to Slack webhook.\n    \n\nThis is straightforward… cron triggers the script → script backs up database → uploads to S3 → sends notification → cleans up old local files\n\n## Implementation\n\n### **Step 1: Configure AWS and Docker**\n\n**AWS Setup**:\n\nFirst, you want configure AWS CLI on your host machine and create S3 buckets for your backups. [Install AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) if it’s not.\n\n```bash\naws configure\n# Enter your credentials and region\n\n# Test access\naws s3 ls\n```\n\n**Docker Setup:**\n\nAdd a volume mount to your database service in `compose.yml`:\n\n```yaml\nservices:\n  db:\n    image: postgres:17.2-bookworm\n    volumes:\n      - db_data:/var/lib/postgresql/data\n      - ./backups:/backups  # Add this for backup files\n    environment:\n      POSTGRES_DB: myapp_db\n      POSTGRES_USER: myapp_user\n      POSTGRES_PASSWORD: ${DB_PASSWORD}\nvolumes:\n  db_data:\n```\n\nRestart your containers to apply the changes:\n\n```bash\ndocker compose up -d\n```\n\n> **Production Note:** I actually use Docker Swarm in production/staging for better orchestration. The backup strategy is pretty much the same. Just update container filters to `docker ps -qf \"name=stackname_db.1\"` instead of `\"name=projectname_db\"`. I'll cover this in the Production Considerations section.\n\n### **Step 2: The Backup Script**\n\nCreate `~/scripts/backup.sh` with three key functions:\n\n1. `run_backup()` - Executes `pg_dump` via `docker exec`, uploads to S3, cleans up old local files\n    \n2. `send_notification()` - POSTs backup status directly to Slack\n    \n3. Retry logic - attempts backup up to 10 times with 10-minute intervals\n    \n\nHere's the simplified structure:\n\n```bash\n#!/bin/bash\nset -euo pipefail\n\nENV=\"${1:-prod}\"\nMAX_RETRIES=10\nRETRY_INTERVAL=600  # 10 minutes\n\n# load configs (Slack webhook, AWS region, etc.)\nsource ~/.backup_env\n\n# environment-specific config\nif [ \"$ENV\" == \"prod\" ]; then\n    DB_NAME=\"myapp_prod\"\n    DB_USER=\"myapp_user\"\n    S3_BUCKET=\"myapp-backups\"\nelse\n    DB_NAME=\"myapp_staging\"\n    DB_USER=\"myapp_user\"\n    S3_BUCKET=\"myapp-staging-backups\"\nfi\n\nBACKUP_FILE=\"backup_$(date +%Y-%m-%dT%H-%M-%S).dump\"\n\nsend_notification() {\n    local status=$1\n    local error_msg=${2:-\"\"}\n    \n    # determine color\n    local color=\"good\"\n    [[ \"$status\" == *\"failure\"* ]] && color=\"danger\"\n    \n    # build the Slack payload\n    local payload=$(cat <<EOF\n{\n  \"text\": \"DB Backup for ${ENV}: ${status}\",\n  \"attachments\": [{\n    \"color\": \"${color}\",\n    \"fields\": [\n      {\"title\": \"Environment\", \"value\": \"${ENV}\", \"short\": true},\n      {\"title\": \"Status\", \"value\": \"${status}\", \"short\": true},\n      {\"title\": \"Backup File\", \"value\": \"${BACKUP_FILE}\", \"short\": false}\n      $([ -n \"$error_msg\" ] && echo \", {\\\"title\\\": \\\"Error\\\", \\\"value\\\": \\\"${error_msg}\\\", \\\"short\\\": false}\")\n    ]\n  }]\n}\nEOF\n)\n    \n    # send to Slack\n    curl -X POST \"$SLACK_WEBHOOK_URL\" \\\n        -H \"Content-Type: application/json\" \\\n        -d \"$payload\" --silent --show-error\n}\n\nrun_backup() {\n    # find database container\n    local container_id=$(docker ps -qf \"name=db\")\n    \n    # run pg_dump inside container\n    docker exec \"$container_id\" \\\n        pg_dump -U $DB_USER -d $DB_NAME \\\n        -Fc -b -v -f /backups/$BACKUP_FILE\n    \n    # Upload to S3\n    aws s3 cp ~/backups/$BACKUP_FILE \\\n        s3://$S3_BUCKET/ --region us-east-1\n    \n    # Keep only last 2 local backups\n    ls -t ~/backups/*.dump | tail -n +3 | xargs -r rm\n}\n\n# Main execution with retry logic\nif run_backup; then\n    send_notification \"success\"\n    exit 0\nelse\n    send_notification \"first_failure\" \"Initial backup failed\"\n    \n    for i in $(seq 1 $MAX_RETRIES); do\n        sleep $RETRY_INTERVAL\n        if run_backup; then\n            send_notification \"success_after_retry\"\n            exit 0\n        fi\n    done\n    \n    send_notification \"final_failure\" \"All retries exhausted\"\n    exit 1\nfi\n```\n\nBreaking down the `docker exec` command with `pg_dump`:\n\n```bash\ndocker exec -d 42f3acbe6d70 pg_dump -U $DB_USER -d $DB_NAME -Fc -b -v -f /backups/$BACKUP_FILE\n│      │     │ │            │        │           │           ││  │  │  | │\n│      │     │ │            │        │           │           ││  │  │  | └─ Output file path with variable.\n│      │     │ │            │        │           │           ││  │  │  └─── File name flag.\n│      │     │ │            │        │           │           ││  │  └────── Specifies verbose mode.\n│      │     │ │            │        │           │           ││  └───────── Include large objects in the dump.\n│      │     │ │            │        │           │           │└──────────── Output a custom-format archive suitable for input into pg_restore\n│      │     │ │            │        │           │           └───────────── Selects the format of the output.\n│      │     │ │            │        │           └───────────────────────── Specifies the name of the database to connect to. \n│      │     │ │            │        └───────────────────────────────────── User name to connect as.\n│      │     │ │            └────────────────────────────────────────────── Export a PostgreSQL database as an SQL script or to other formats\n│      │     │ └─────────────────────────────────────────────────────────── Container ID \n│      │     └───────────────────────────────────────────────────────────── Detached mode: run command in the background\n└──────└─────────────────────────────────────────────────────────────────── Execute a command in a running container\n```\n\n**Key decisions explained:**\n\n* **Why** `-Fc` format? Custom format is compressed and allows selective restoration of specific tables.\n    \n* **Why retry logic?** Networks fail. AWS can hiccup. Retries make the system resilient.\n    \n* **Why keep 2 local backups?** Quick access for recent restores without hitting S3, but doesn't fill the disk.\n    \n* **Why direct Slack posting?** Fewer dependencies. If you already have a notification service, you could POST there instead and let it handle formatting. [Setting up a Slack webhook](https://docs.slack.dev/messaging/sending-messages-using-incoming-webhooks/). What it looks like on Slack:\n    \n    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1765859755306/2303c827-e5b9-44b3-ad3c-03906facdc96.png align=\"center\")\n    \n\nMake the script executable:\n\n```bash\nchmod +x ~/scripts/backup.sh\n```\n\nCreate the environment file with your secrets:\n\n```bash\ncat > ~/.backup_env << 'EOF'\nSLACK_WEBHOOK_URL=\"https://hooks.slack.com/services/YOUR/WEBHOOK/URL\"\nEOF\n\nchmod 600 ~/.backup_env # only the file's owner has read and write access\n```\n\n### Step 3: Automation with Cron\n\nSchedule the backups to run automatically:\n\n```bash\ncrontab -e\n\n# add these lines:\n# Production - daily at 2 AM\n0 2 * * * /home/username/scripts/backup.sh prod >> /home/username/.db_backup_logs/backup-prod.log 2>&1\n\n# Staging - Sundays at 2 AM  \n0 2 * * 0 /home/username/scripts/backup.sh staging >> /home/username/.db_backup_logs/backup-staging.log 2>&1\n\n# Cleanup old logs - daily at 3 AM\n0 3 * * * find /home/username/.db_backup_logs -name \"*.log\" -mtime +30 -delete\n```\n\n```bash\nmkdir -p ~/.db_backup_logs\n```\n\n### Step 4: The Restoration Script\n\nBackups are useless if you can't restore them. You could do it manually, but why not just use Bash scripting as well? Create `~/scripts/db-restore.sh`:\n\n```bash\n#!/bin/bash\nset -euo pipefail\n\n# Usage: ./db-restore.sh prod backup_2025-12-13T02-00-00.dump [--full]\n\nENV=\"${1}\"\nBACKUP_FILE=\"${2}\"\nFULL_RESTORE=\"${3:-}\"\n\n# Download from S3\naws s3 cp s3://myapp-backups/$BACKUP_FILE ~/restore/\n\n# Copy to container\nCONTAINER_ID=$(docker ps -qf \"name=db\")\ndocker cp ~/restore/$BACKUP_FILE $CONTAINER_ID:/tmp/\n\n# Restore (with confirmation prompt)\nif [ \"$FULL_RESTORE\" == \"--full\" ]; then\n    # Drop and recreate database\n    docker exec -i $CONTAINER_ID psql -U postgres <<EOF\nDROP DATABASE IF EXISTS myapp_prod;\nCREATE DATABASE myapp_prod;\nEOF\nfi\n\n# Run pg_restore\ndocker exec $CONTAINER_ID \\\n    pg_restore -U myapp_user -d myapp_prod \\\n    --clean --if-exists -v /tmp/$BACKUP_FILE\n\n# Cleanup\ndocker exec $CONTAINER_ID rm /tmp/$BACKUP_FILE\nrm ~/restore/$BACKUP_FILE\n```\n\nMake it executable:\n\n```bash\nchmod +x ~/scripts/db-restore.sh\n```\n\n**Testing restoration is very important.** You might want to do this regularly, perhaps monthly.\n\n```bash\n# Download latest prod backup\nLATEST=$(aws s3 ls s3://myapp-backups/ | sort | tail -n 1 | awk '{print $4}')\n\n# Restore to staging\n./scripts/db-restore.sh staging $LATEST --full\n```\n\n## Production Considerations\n\n### Docker Swarm vs Compose\n\nIn production, I use Docker Swarm instead of Compose for better orchestration and secrets management. It’s actually the same backup strategy with a few changes:\n\n**Container naming:**\n\n* Compose: `projectname_db_1`\n    \n* Stack: `stackname_db.1.container_id`\n    \n\n**Finding containers:**\n\n```bash\n# works for both\ndocker ps -qf \"name=db\"\n```\n\n**Secrets management:**\n\nDocker Swarm handles secrets more securely:\n\n```yaml\nservices:\n  db:\n    secrets:\n      - db_password\n    environment:\n      POSTGRES_PASSWORD_FILE: /run/secrets/db_password\n\nsecrets:\n  db_password:\n    external: true\n```\n\nCreate the secret:\n\n```bash\n# I find stdin to be easier to work with\nprintf \"your-database-password-here\" | docker secret create db-password -\n```\n\nThen in the backup script, read the password from inside the container:\n\n```bash\ndocker exec $CONTAINER_ID sh -c \\\n    \"PGPASSWORD=\\$(cat /run/secrets/db_password) pg_dump ...\"\n```\n\n### S3 Lifecycle Policies\n\nIf you want to prevent backup costs from growing forever, consider auto-deleting old backups:\n\n1. Go to S3 Console → Your bucket → Management → Lifecycle rules\n    \n2. Create rule: Delete objects older than 30 days\n    \n3. Apply to prefix: `prod-db-backups/`\n    \n\n### Monitoring\n\nBeyond Slack notifications, you could also:\n\n* Review logs: `tail -50 ~/.db_backup_logs/backup-prod.log`\n    \n* Check S3: `aws s3 ls s3://myapp-backups/ | tail`\n    \n* Test restoration at intervals (as mentioned above)\n    \n\n## The Result\n\n* Automated nightly backups\n    \n* Slack notifications keeping us notified\n    \n* Testing restoration process\n    \n* Off-site storage in S3\n    \n* and of course, peace of mind, heh…\n    \n\nS3 buckets are way cheaper than RDS instances, so this to me is part of keeping costs low for projects without sacrificing the safety net of reliable backups.\n\n## Try It Yourself\n\nFull scripts available in this [GitHub Gist](https://gist.github.com/theolujay/b7131a0d6553299507124c46fadfd4a7). The system is environment-agnostic. Only update the configuration variables for your setup.\n\n**Questions? Drop them in the comments.**\n\n---\n\n<sub>Originally published at [blog.theolujay.dev](https://blog.theolujay.dev/pg-backup-docker-s3-slack)</sub>",
      "json_metadata": "{\"app\":\"peakd/2025.12.9\",\"format\":\"markdown\",\"tags\":[\"dev\",\"bash\",\"docker\",\"postgresql\",\"aws\",\"slack\",\"backend\"],\"users\":[],\"image\":[\"https://cdn.hashnode.com/res/hashnode/image/upload/v1765867594016/6a876012-b7bd-40de-a696-d3908ccf69ab.png?w=1600&amp;h=840&amp;fit=crop&amp;crop=entropy&amp;auto=compress,format&amp;format=webp\",\"https://cdn.hashnode.com/res/hashnode/image/upload/v1765859755306/2303c827-e5b9-44b3-ad3c-03906facdc96.png\"]}",
      "parent_author": "",
      "parent_permlink": "dev",
      "permlink": "pg-backup-docker-s3-slack",
      "title": "PostgreSQL Backup System for Docker with S3 and Slack"
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2026-01-01T13:00:03",
  "trx_id": "41931e4949d9ae74ad233eaaebcf82a28b797814",
  "trx_in_block": 16,
  "virtual_op": false
}
theolujayupdated options for re-hivebuzz-t85dzh
2025/12/31 18:57:45
allow curation rewardstrue
allow votestrue
authortheolujay
extensions[]
max accepted payout1000000.000 HBD
percent hbd0
permlinkre-hivebuzz-t85dzh
Transaction InfoBlock #102532893/Trx c480388a6cca95ee2150cf20dbde9f2b00b192d4
View Raw JSON Data
{
  "block": 102532893,
  "op": [
    "comment_options",
    {
      "allow_curation_rewards": true,
      "allow_votes": true,
      "author": "theolujay",
      "extensions": [],
      "max_accepted_payout": "1000000.000 HBD",
      "percent_hbd": 0,
      "permlink": "re-hivebuzz-t85dzh"
    }
  ],
  "op_in_trx": 1,
  "timestamp": "2025-12-31T18:57:45",
  "trx_id": "c480388a6cca95ee2150cf20dbde9f2b00b192d4",
  "trx_in_block": 9,
  "virtual_op": false
}
2025/12/31 18:57:45
authortheolujay
bodySTOP
json metadata{"tags":["dev"],"app":"peakd/2025.12.9","image":[],"users":[]}
parent authorhivebuzz
parent permlinknotify-1767206113
permlinkre-hivebuzz-t85dzh
title
Transaction InfoBlock #102532893/Trx c480388a6cca95ee2150cf20dbde9f2b00b192d4
View Raw JSON Data
{
  "block": 102532893,
  "op": [
    "comment",
    {
      "author": "theolujay",
      "body": "STOP",
      "json_metadata": "{\"tags\":[\"dev\"],\"app\":\"peakd/2025.12.9\",\"image\":[],\"users\":[]}",
      "parent_author": "hivebuzz",
      "parent_permlink": "notify-1767206113",
      "permlink": "re-hivebuzz-t85dzh",
      "title": ""
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2025-12-31T18:57:45",
  "trx_id": "c480388a6cca95ee2150cf20dbde9f2b00b192d4",
  "trx_in_block": 9,
  "virtual_op": false
}
theolujaydeleted a comment or post
2025/12/31 18:56:21
authortheolujay
permlinkre-hivebuzz-t85dx6
Transaction InfoBlock #102532865/Trx 0692119d31de000271dc354adf1081370be0b700
View Raw JSON Data
{
  "block": 102532865,
  "op": [
    "delete_comment",
    {
      "author": "theolujay",
      "permlink": "re-hivebuzz-t85dx6"
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2025-12-31T18:56:21",
  "trx_id": "0692119d31de000271dc354adf1081370be0b700",
  "trx_in_block": 5,
  "virtual_op": false
}
theolujayupdated options for re-hivebuzz-t85dx6
2025/12/31 18:56:03
allow curation rewardstrue
allow votestrue
authortheolujay
extensions[]
max accepted payout1000000.000 HBD
percent hbd0
permlinkre-hivebuzz-t85dx6
Transaction InfoBlock #102532859/Trx 4cfe5e6f3a71e53fa940e5d596931a79fbb23823
View Raw JSON Data
{
  "block": 102532859,
  "op": [
    "comment_options",
    {
      "allow_curation_rewards": true,
      "allow_votes": true,
      "author": "theolujay",
      "extensions": [],
      "max_accepted_payout": "1000000.000 HBD",
      "percent_hbd": 0,
      "permlink": "re-hivebuzz-t85dx6"
    }
  ],
  "op_in_trx": 1,
  "timestamp": "2025-12-31T18:56:03",
  "trx_id": "4cfe5e6f3a71e53fa940e5d596931a79fbb23823",
  "trx_in_block": 9,
  "virtual_op": false
}
2025/12/31 18:56:03
authortheolujay
bodySTOP
json metadata{"tags":["dev"],"app":"peakd/2025.12.9","image":[],"users":[]}
parent authorhivebuzz
parent permlinknotify-1767206113
permlinkre-hivebuzz-t85dx6
title
Transaction InfoBlock #102532859/Trx 4cfe5e6f3a71e53fa940e5d596931a79fbb23823
View Raw JSON Data
{
  "block": 102532859,
  "op": [
    "comment",
    {
      "author": "theolujay",
      "body": "STOP",
      "json_metadata": "{\"tags\":[\"dev\"],\"app\":\"peakd/2025.12.9\",\"image\":[],\"users\":[]}",
      "parent_author": "hivebuzz",
      "parent_permlink": "notify-1767206113",
      "permlink": "re-hivebuzz-t85dx6",
      "title": ""
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2025-12-31T18:56:03",
  "trx_id": "4cfe5e6f3a71e53fa940e5d596931a79fbb23823",
  "trx_in_block": 9,
  "virtual_op": false
}
2025/12/31 18:35:15
authorhivebuzz
bodyCongratulations @theolujay! You have completed the following achievement on the Hive blockchain And have been rewarded with New badge(s) <table><tr><td><img src="https://images.hive.blog/60x70/https://hivebuzz.me/@theolujay/upvoted.png?202512311824"></td><td>You received more than 10 upvotes.<br>Your next target is to reach 50 upvotes.</td></tr> </table> <sub>_You can view your badges on [your board](https://hivebuzz.me/@theolujay) and compare yourself to others in the [Ranking](https://hivebuzz.me/ranking)_</sub> <sub>_If you no longer want to receive notifications, reply to this comment with the word_ `STOP`</sub> **Check out our last posts:** <table><tr><td><a href="/hive-122221/@hivebuzz/pum-202601"><img src="https://images.hive.blog/64x128/https://i.imgur.com/M9RD8KS.png"></a></td><td><a href="/hive-122221/@hivebuzz/pum-202601">Be ready for the January edition of the Hive Power Up Month!</a></td></tr><tr><td><a href="/hive-122221/@hivebuzz/pud-202601"><img src="https://images.hive.blog/64x128/https://i.imgur.com/805FIIt.jpg"></a></td><td><a href="/hive-122221/@hivebuzz/pud-202601">Hive Power Up Day - January 1st 2026</a></td></tr></table>
json metadata{"image":["https://hivebuzz.me/notify.t6.png"]}
parent authortheolujay
parent permlinkhow-cloudflare-fixed-my-geo-locked-blog
permlinknotify-1767206113
title
Transaction InfoBlock #102532445/Trx 3328edfc44db7290f073ea9cd2a67f2ad72fed25
View Raw JSON Data
{
  "block": 102532445,
  "op": [
    "comment",
    {
      "author": "hivebuzz",
      "body": "Congratulations @theolujay! You have completed the following achievement on the Hive blockchain And have been rewarded with New badge(s)\n\n<table><tr><td><img src=\"https://images.hive.blog/60x70/https://hivebuzz.me/@theolujay/upvoted.png?202512311824\"></td><td>You received more than 10 upvotes.<br>Your next target is to reach 50 upvotes.</td></tr>\n</table>\n\n<sub>_You can view your badges on [your board](https://hivebuzz.me/@theolujay) and compare yourself to others in the [Ranking](https://hivebuzz.me/ranking)_</sub>\n<sub>_If you no longer want to receive notifications, reply to this comment with the word_ `STOP`</sub>\n\n\n\n**Check out our last posts:**\n<table><tr><td><a href=\"/hive-122221/@hivebuzz/pum-202601\"><img src=\"https://images.hive.blog/64x128/https://i.imgur.com/M9RD8KS.png\"></a></td><td><a href=\"/hive-122221/@hivebuzz/pum-202601\">Be ready for the January edition of the Hive Power Up Month!</a></td></tr><tr><td><a href=\"/hive-122221/@hivebuzz/pud-202601\"><img src=\"https://images.hive.blog/64x128/https://i.imgur.com/805FIIt.jpg\"></a></td><td><a href=\"/hive-122221/@hivebuzz/pud-202601\">Hive Power Up Day - January 1st 2026</a></td></tr></table>",
      "json_metadata": "{\"image\":[\"https://hivebuzz.me/notify.t6.png\"]}",
      "parent_author": "theolujay",
      "parent_permlink": "how-cloudflare-fixed-my-geo-locked-blog",
      "permlink": "notify-1767206113",
      "title": ""
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2025-12-31T18:35:15",
  "trx_id": "3328edfc44db7290f073ea9cd2a67f2ad72fed25",
  "trx_in_block": 8,
  "virtual_op": false
}
2025/12/31 15:55:15
authortheolujay
pending payout0.042 HBD
permlinkhow-cloudflare-fixed-my-geo-locked-blog
rshares24736995003
total vote weight344563567329
votermayor-001
weight24736995003
Transaction InfoBlock #102529252/Trx 7e22562ed0202e23421793c22f3683da9dc246e0
View Raw JSON Data
{
  "block": 102529252,
  "op": [
    "effective_comment_vote",
    {
      "author": "theolujay",
      "pending_payout": "0.042 HBD",
      "permlink": "how-cloudflare-fixed-my-geo-locked-blog",
      "rshares": 24736995003,
      "total_vote_weight": 344563567329,
      "voter": "mayor-001",
      "weight": 24736995003
    }
  ],
  "op_in_trx": 1,
  "timestamp": "2025-12-31T15:55:15",
  "trx_id": "7e22562ed0202e23421793c22f3683da9dc246e0",
  "trx_in_block": 19,
  "virtual_op": true
}
2025/12/31 15:55:15
authortheolujay
permlinkhow-cloudflare-fixed-my-geo-locked-blog
votermayor-001
weight5250 (52.50%)
Transaction InfoBlock #102529252/Trx 7e22562ed0202e23421793c22f3683da9dc246e0
View Raw JSON Data
{
  "block": 102529252,
  "op": [
    "vote",
    {
      "author": "theolujay",
      "permlink": "how-cloudflare-fixed-my-geo-locked-blog",
      "voter": "mayor-001",
      "weight": 5250
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2025-12-31T15:55:15",
  "trx_id": "7e22562ed0202e23421793c22f3683da9dc246e0",
  "trx_in_block": 19,
  "virtual_op": false
}
2025/12/31 15:50:24
authortheolujay
body@@ -1,13 +1,63 @@ -This blog +My blog at %5Bblog.theolujay.dev%5D(https://blog.theolujay.dev) mig
json metadata{"app":"peakd/2025.12.9","format":"markdown","image":["https://cdn.hashnode.com/res/hashnode/image/upload/v1764092979473/c7861635-227b-420b-8b53-ae068936f212.png","https://cdn.hashnode.com/res/hashnode/image/upload/v1764093484839/1312f52a-3175-4abb-a4fa-165c0f926d23.png"],"tags":["dev","cloud","connectivity","backend","tech"],"users":[]}
parent author
parent permlinkdev
permlinkhow-cloudflare-fixed-my-geo-locked-blog
titleHow Cloudflare Fixed My “Geo-Locked” Blog
Transaction InfoBlock #102529156/Trx f45361929d1384ce9818fc611eec3b6c1284003f
View Raw JSON Data
{
  "block": 102529156,
  "op": [
    "comment",
    {
      "author": "theolujay",
      "body": "@@ -1,13 +1,63 @@\n-This blog\n+My blog at %5Bblog.theolujay.dev%5D(https://blog.theolujay.dev)\n  mig\n",
      "json_metadata": "{\"app\":\"peakd/2025.12.9\",\"format\":\"markdown\",\"image\":[\"https://cdn.hashnode.com/res/hashnode/image/upload/v1764092979473/c7861635-227b-420b-8b53-ae068936f212.png\",\"https://cdn.hashnode.com/res/hashnode/image/upload/v1764093484839/1312f52a-3175-4abb-a4fa-165c0f926d23.png\"],\"tags\":[\"dev\",\"cloud\",\"connectivity\",\"backend\",\"tech\"],\"users\":[]}",
      "parent_author": "",
      "parent_permlink": "dev",
      "permlink": "how-cloudflare-fixed-my-geo-locked-blog",
      "title": "How Cloudflare Fixed My “Geo-Locked” Blog"
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2025-12-31T15:50:24",
  "trx_id": "f45361929d1384ce9818fc611eec3b6c1284003f",
  "trx_in_block": 1,
  "virtual_op": false
}
2025/12/31 13:52:33
authortheolujay
pending payout0.039 HBD
permlinkhow-cloudflare-fixed-my-geo-locked-blog
rshares38684452630
total vote weight319826572326
votererica005
weight38684452630
Transaction InfoBlock #102526804/Trx 349e4f5b67ee67ecacc184bb8377a5069dc148bd
View Raw JSON Data
{
  "block": 102526804,
  "op": [
    "effective_comment_vote",
    {
      "author": "theolujay",
      "pending_payout": "0.039 HBD",
      "permlink": "how-cloudflare-fixed-my-geo-locked-blog",
      "rshares": 38684452630,
      "total_vote_weight": 319826572326,
      "voter": "erica005",
      "weight": 38684452630
    }
  ],
  "op_in_trx": 1,
  "timestamp": "2025-12-31T13:52:33",
  "trx_id": "349e4f5b67ee67ecacc184bb8377a5069dc148bd",
  "trx_in_block": 36,
  "virtual_op": true
}
2025/12/31 13:52:33
authortheolujay
permlinkhow-cloudflare-fixed-my-geo-locked-blog
votererica005
weight10000 (100.00%)
Transaction InfoBlock #102526804/Trx 349e4f5b67ee67ecacc184bb8377a5069dc148bd
View Raw JSON Data
{
  "block": 102526804,
  "op": [
    "vote",
    {
      "author": "theolujay",
      "permlink": "how-cloudflare-fixed-my-geo-locked-blog",
      "voter": "erica005",
      "weight": 10000
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2025-12-31T13:52:33",
  "trx_id": "349e4f5b67ee67ecacc184bb8377a5069dc148bd",
  "trx_in_block": 36,
  "virtual_op": false
}
2025/12/31 13:35:42
authortheolujay
pending payout0.035 HBD
permlinkhow-cloudflare-fixed-my-geo-locked-blog
rshares281142119696
total vote weight281142119696
voterakdx
weight281142119696
Transaction InfoBlock #102526467/Trx d7e6476aecb43a85fb71157d60bc33483c02f078
View Raw JSON Data
{
  "block": 102526467,
  "op": [
    "effective_comment_vote",
    {
      "author": "theolujay",
      "pending_payout": "0.035 HBD",
      "permlink": "how-cloudflare-fixed-my-geo-locked-blog",
      "rshares": 281142119696,
      "total_vote_weight": 281142119696,
      "voter": "akdx",
      "weight": 281142119696
    }
  ],
  "op_in_trx": 1,
  "timestamp": "2025-12-31T13:35:42",
  "trx_id": "d7e6476aecb43a85fb71157d60bc33483c02f078",
  "trx_in_block": 1,
  "virtual_op": true
}
2025/12/31 13:35:42
authortheolujay
permlinkhow-cloudflare-fixed-my-geo-locked-blog
voterakdx
weight6000 (60.00%)
Transaction InfoBlock #102526467/Trx d7e6476aecb43a85fb71157d60bc33483c02f078
View Raw JSON Data
{
  "block": 102526467,
  "op": [
    "vote",
    {
      "author": "theolujay",
      "permlink": "how-cloudflare-fixed-my-geo-locked-blog",
      "voter": "akdx",
      "weight": 6000
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2025-12-31T13:35:42",
  "trx_id": "d7e6476aecb43a85fb71157d60bc33483c02f078",
  "trx_in_block": 1,
  "virtual_op": false
}
2025/12/31 13:00:03
allow curation rewardstrue
allow votestrue
authortheolujay
extensions[]
max accepted payout1000000.000 HBD
percent hbd0
permlinkhow-cloudflare-fixed-my-geo-locked-blog
Transaction InfoBlock #102525757/Trx 74fb67ff4203c7db39beda6c0c48652c4f717192
View Raw JSON Data
{
  "block": 102525757,
  "op": [
    "comment_options",
    {
      "allow_curation_rewards": true,
      "allow_votes": true,
      "author": "theolujay",
      "extensions": [],
      "max_accepted_payout": "1000000.000 HBD",
      "percent_hbd": 0,
      "permlink": "how-cloudflare-fixed-my-geo-locked-blog"
    }
  ],
  "op_in_trx": 1,
  "timestamp": "2025-12-31T13:00:03",
  "trx_id": "74fb67ff4203c7db39beda6c0c48652c4f717192",
  "trx_in_block": 14,
  "virtual_op": false
}
2025/12/31 13:00:03
authortheolujay
bodyThis blog might have been inaccessible to you if you’re not in the US or UK or using a VPN. Since I plugged in my own domain to Hashnode, I almost never could visit my own page without a VPN. I knew I followed the setup instructions right, so after feeling frustrated for so long, I concluded that Hashnode was down or something… yet I resisted defeat, went down a rabbit hole, and found something fascinating. https://cdn.hashnode.com/res/hashnode/image/upload/v1764093484839/1312f52a-3175-4abb-a4fa-165c0f926d23.png I’ve been learning about DNS and cloud technologies lately. Problems that arose at work have me figuring out things I might never have outside the job. And so when the internet downtimes we experienced lately occured - think AWS and Cloudflare - I could fairly understand the explanations for what happened. Even at that, I didn’t quite realize how big of a deal Cloudflare is today until they turned out to be the answer to the problems with my blog. Hashnode was apparently serving my blog from Vercel, but the DNS path from where I’m from, Nigeria, to that Vercel edge location was basically trash. I mean, it somehow wasn’t working. So even when I’d curl https://blog.theolujay.dev, it would just stall and nothing would happen. Perhaps it was my ISP, but I figured that whatever traffic route it was taking wasn’t cutting it. The VPN “fixed” it because, you know, it teleported my traffic to a different location, which was usually the US, where Vercel’s edge was fine. I surely couldn’t write to Hashnode or my ISP to move the world for me, just because I was starting to write again. Down the hole I began to dig to find answers, I realized that Cloudflare is perfectly positioned for this type of problem. And that’s how I started to read and learn more about them and this whole [“Connectivity Cloud” concept](https://blog.cloudflare.com/welcome-to-connectivity-cloud/). Prior to this situation of mine, I had already been inspired by their explanation of the [Cloudflare outage on November 18, 2025](https://blog.cloudflare.com/18-november-2025-outage/). https://cdn.hashnode.com/res/hashnode/image/upload/v1764092979473/c7861635-227b-420b-8b53-ae068936f212.png Namecheap is my domain registrar. I was using their DNS system, which wasn’t helping me out. I signed up on Cloudflare, got new nameservers from them, and added them to my domain settings on Namecheap. After it took a while to activate, my blog was now accessible without VPN. It really was as easy as that. In other words, DNS queries for my domain now hit Cloudflare’s Anycast network (a nearby data center) and pull content from Hashnode/Vercel over their network. And then my stuff is accessible from Cloudflare’s edge. So apparently, Hashnode wasn’t the problem. It was a routing failure between where I am and their infrastructure. --- <sub>Originally published at [blog.theolujay.dev](https://blog.theolujay.dev/how-cloudflare-fixed-my-geo-locked-blog)</sub>
json metadata{"app":"peakd/2025.12.9","format":"markdown","tags":["dev","cloud","connectivity","backend","tech"],"users":[],"image":["https://cdn.hashnode.com/res/hashnode/image/upload/v1764092979473/c7861635-227b-420b-8b53-ae068936f212.png","https://cdn.hashnode.com/res/hashnode/image/upload/v1764093484839/1312f52a-3175-4abb-a4fa-165c0f926d23.png"]}
parent author
parent permlinkdev
permlinkhow-cloudflare-fixed-my-geo-locked-blog
titleHow Cloudflare Fixed My “Geo-Locked” Blog
Transaction InfoBlock #102525757/Trx 74fb67ff4203c7db39beda6c0c48652c4f717192
View Raw JSON Data
{
  "block": 102525757,
  "op": [
    "comment",
    {
      "author": "theolujay",
      "body": "This blog might have been inaccessible to you if you’re not in the US or UK or using a VPN. Since I plugged in my own domain to Hashnode, I almost never could visit my own page without a VPN. I knew I followed the setup instructions right, so after feeling frustrated for so long, I concluded that Hashnode was down or something… yet I resisted defeat, went down a rabbit hole, and found something fascinating.\n\nhttps://cdn.hashnode.com/res/hashnode/image/upload/v1764093484839/1312f52a-3175-4abb-a4fa-165c0f926d23.png\n\nI’ve been learning about DNS and cloud technologies lately. Problems that arose at work have me figuring out things I might never have outside the job. And so when the internet downtimes we experienced lately occured - think AWS and Cloudflare - I could fairly understand the explanations for what happened. Even at that, I didn’t quite realize how big of a deal Cloudflare is today until they turned out to be the answer to the problems with my blog.\n\nHashnode was apparently serving my blog from Vercel, but the DNS path from where I’m from, Nigeria, to that Vercel edge location was basically trash. I mean, it somehow wasn’t working. So even when I’d curl https://blog.theolujay.dev, it would just stall and nothing would happen. Perhaps it was my ISP, but I figured that whatever traffic route it was taking wasn’t cutting it. The VPN “fixed” it because, you know, it teleported my traffic to a different location, which was usually the US, where Vercel’s edge was fine.\n\nI surely couldn’t write to Hashnode or my ISP to move the world for me, just because I was starting to write again. Down the hole I began to dig to find answers, I realized that Cloudflare is perfectly positioned for this type of problem. And that’s how I started to read and learn more about them and this whole [“Connectivity Cloud” concept](https://blog.cloudflare.com/welcome-to-connectivity-cloud/). Prior to this situation of mine, I had already been inspired by their explanation of the [Cloudflare outage on November 18, 2025](https://blog.cloudflare.com/18-november-2025-outage/).\n\nhttps://cdn.hashnode.com/res/hashnode/image/upload/v1764092979473/c7861635-227b-420b-8b53-ae068936f212.png\n\nNamecheap is my domain registrar. I was using their DNS system, which wasn’t helping me out. I signed up on Cloudflare, got new nameservers from them, and added them to my domain settings on Namecheap. After it took a while to activate, my blog was now accessible without VPN. It really was as easy as that.\n\nIn other words, DNS queries for my domain now hit Cloudflare’s Anycast network (a nearby data center) and pull content from Hashnode/Vercel over their network. And then my stuff is accessible from Cloudflare’s edge. So apparently, Hashnode wasn’t the problem. It was a routing failure between where I am and their infrastructure.\n\n---\n\n<sub>Originally published at [blog.theolujay.dev](https://blog.theolujay.dev/how-cloudflare-fixed-my-geo-locked-blog)</sub>",
      "json_metadata": "{\"app\":\"peakd/2025.12.9\",\"format\":\"markdown\",\"tags\":[\"dev\",\"cloud\",\"connectivity\",\"backend\",\"tech\"],\"users\":[],\"image\":[\"https://cdn.hashnode.com/res/hashnode/image/upload/v1764092979473/c7861635-227b-420b-8b53-ae068936f212.png\",\"https://cdn.hashnode.com/res/hashnode/image/upload/v1764093484839/1312f52a-3175-4abb-a4fa-165c0f926d23.png\"]}",
      "parent_author": "",
      "parent_permlink": "dev",
      "permlink": "how-cloudflare-fixed-my-geo-locked-blog",
      "title": "How Cloudflare Fixed My “Geo-Locked” Blog"
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2025-12-31T13:00:03",
  "trx_id": "74fb67ff4203c7db39beda6c0c48652c4f717192",
  "trx_in_block": 14,
  "virtual_op": false
}
2025/12/30 15:51:30
authortheolujay
pending payout0.000 HBD
permlinkpractical-habits-for-productive-python-development
rshares947035228
total vote weight947035228
voterlolz.byte
weight947035228
Transaction InfoBlock #102500446/Trx 3bfa4dcd2bcc5a81d751106fb5d48b823b5857f9
View Raw JSON Data
{
  "block": 102500446,
  "op": [
    "effective_comment_vote",
    {
      "author": "theolujay",
      "pending_payout": "0.000 HBD",
      "permlink": "practical-habits-for-productive-python-development",
      "rshares": 947035228,
      "total_vote_weight": 947035228,
      "voter": "lolz.byte",
      "weight": 947035228
    }
  ],
  "op_in_trx": 1,
  "timestamp": "2025-12-30T15:51:30",
  "trx_id": "3bfa4dcd2bcc5a81d751106fb5d48b823b5857f9",
  "trx_in_block": 15,
  "virtual_op": true
}
2025/12/30 15:51:30
authortheolujay
permlinkpractical-habits-for-productive-python-development
voterlolz.byte
weight10000 (100.00%)
Transaction InfoBlock #102500446/Trx 3bfa4dcd2bcc5a81d751106fb5d48b823b5857f9
View Raw JSON Data
{
  "block": 102500446,
  "op": [
    "vote",
    {
      "author": "theolujay",
      "permlink": "practical-habits-for-productive-python-development",
      "voter": "lolz.byte",
      "weight": 10000
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2025-12-30T15:51:30",
  "trx_id": "3bfa4dcd2bcc5a81d751106fb5d48b823b5857f9",
  "trx_in_block": 15,
  "virtual_op": false
}
theolujaypublished a new post: initial-commit
2025/12/30 13:00:39
authortheolujay
body@@ -510,157 +510,35 @@ at I -'ll bring here. New posts will now likely come here first and the canonical URL embedded on my blog at Hashnode. That way, SEO for my work isn't hurt + might first republish here .%0A%0AI
json metadata{"app":"peakd/2025.12.9","format":"markdown","image":[],"tags":["dev","software","tech","backend","devops"],"users":["olujay."]}
parent author
parent permlinkdev
permlinkinitial-commit
titlehere again
Transaction InfoBlock #102497035/Trx fd491fae94c271b6b984d14d14fa21f72d5506a4
View Raw JSON Data
{
  "block": 102497035,
  "op": [
    "comment",
    {
      "author": "theolujay",
      "body": "@@ -510,157 +510,35 @@\n at I\n-'ll bring here. New posts will now likely come here first and the canonical URL embedded on my blog at Hashnode. That way, SEO for my work isn't hurt\n+ might first republish here\n .%0A%0AI\n",
      "json_metadata": "{\"app\":\"peakd/2025.12.9\",\"format\":\"markdown\",\"image\":[],\"tags\":[\"dev\",\"software\",\"tech\",\"backend\",\"devops\"],\"users\":[\"olujay.\"]}",
      "parent_author": "",
      "parent_permlink": "dev",
      "permlink": "initial-commit",
      "title": "here again"
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2025-12-30T13:00:39",
  "trx_id": "fd491fae94c271b6b984d14d14fa21f72d5506a4",
  "trx_in_block": 4,
  "virtual_op": false
}
2025/12/30 13:00:03
allow curation rewardstrue
allow votestrue
authortheolujay
extensions[]
max accepted payout1000000.000 HBD
percent hbd0
permlinkpractical-habits-for-productive-python-development
Transaction InfoBlock #102497023/Trx c1556386551d9a86ef2f831ee3e7ca0be1a995a5
View Raw JSON Data
{
  "block": 102497023,
  "op": [
    "comment_options",
    {
      "allow_curation_rewards": true,
      "allow_votes": true,
      "author": "theolujay",
      "extensions": [],
      "max_accepted_payout": "1000000.000 HBD",
      "percent_hbd": 0,
      "permlink": "practical-habits-for-productive-python-development"
    }
  ],
  "op_in_trx": 1,
  "timestamp": "2025-12-30T13:00:03",
  "trx_id": "c1556386551d9a86ef2f831ee3e7ca0be1a995a5",
  "trx_in_block": 13,
  "virtual_op": false
}
2025/12/30 13:00:03
authortheolujay
body99.99% efficiency sounds great on paper. In practice you’re usually one better tool or one small habit away from not wasting an hour on installs, CI runs, or repetitive terminal typing. Here are three practical changes I use that save time every day. ## 1) Use `uv` instead of raw `pip`/`venv` `uv` is a modern, Rust-powered package & project manager that combines `virtualenv`, `pip`, and `pipx`, and it’s really fast. On projects with many dependencies, you’ll see massive wins with it. Here’s a quick benchmark takeaway: on one of my projects `pip install -r requirements.txt` took ~150s while `uv pip install` completed in ~1s. It’s needless to say you start projects quicker and your app builds are significantly faster. **Install uv** * macOS / Linux (installer script): ```bash curl -LsSf https://astral.sh/uv/install.sh | sh ``` * Homebrew: ```bash brew install uv ``` * Windows (PowerShell installer / winget / scoop) — see official docs for options and latest instructions. (\[Astral Docs\]\[2\]) **Useful \`uv\` commands** ```bash # create venv (uses .venv by default) uv venv # install packages from pyproject or requirements via uv uv pip install -r requirements.txt # install only non-dev groups from pyproject uv sync --no-dev # add packages to groups uv add --group dev --group test ``` I can then run a command like the one below to install just the main packages. ```plaintext uv sync --no-dev ``` Or if I wanted specific (or all) groups, I could do that too. ```plaintext uv add --group dev --group test ``` There are many more interesting ways to use `uv`, like running some commands faster, but that’ll be in a future post. ## 2) Bash aliases Stop retyping long commands. You could put frequently used aliases in `~/.bash_aliases` (or `~/.bashrc`) and use them instead. Practical examples I use ```bash alias ptree='tree -I "__pycache__|*.pyc|.venv|.git|node_modules" --dirsfirst -L 3' alias py='python' alias pyma='python3 manage.py' alias entervenv='source .venv/bin/activate' alias dc='docker compose' alias gcm='git commit -m' ``` ## 3) Small scripts &gt; manual workflows Updating secrets in a running Swarm stack, for example, is a multi-step, error-prone process. Although it’s not something you’d frequently do, it’s all the same a tedious process. Wrap it in a script so you don’t do it wrong at 3 AM. ```bash #!/bin/bash # Usage: ./update-secret.sh secret-name secret-value [stack-name] set -euo pipefail SECRET_NAME=$1 SECRET_VALUE=$2 STACK_NAME=${3:-vmlc-prod} if [ -z "$SECRET_NAME" ] || [ -z "$SECRET_VALUE" ]; then echo "Usage: $0 <secret_name> <secret_value> [stack_name]" exit 1 fi TEMP_SECRET_NAME="${SECRET_NAME}-temp" printf "%s" "$SECRET_VALUE" | docker secret create "$TEMP_SECRET_NAME" - for service in db-migrate django celery_beat celery_worker; do docker service update \ --secret-rm "$SECRET_NAME" \ --secret-add "source=$TEMP_SECRET_NAME,target=$SECRET_NAME" \ "${STACK_NAME}_${service}" > /dev/null done sleep 10 docker secret rm "$SECRET_NAME" printf "%s" "$SECRET_VALUE" | docker secret create "$SECRET_NAME" - for service in db-migrate django celery_beat celery_worker; do docker service update \ --secret-rm "$TEMP_SECRET_NAME" \ --secret-add "source=$SECRET_NAME,target=$SECRET_NAME" \ "${STACK_NAME}_${service}" > /dev/null done sleep 10 docker secret rm "$TEMP_SECRET_NAME" echo "Done! Secret $SECRET_NAME updated successfully." ``` If you copy this, adapt the \`service\` list to match your stack and test on staging first. ### Bonus: prompt info at-a-glance Add PS1 bits so your shell prompt shows whether you’re in a venv, the current git branch, and Docker context. There are various ways to customize it. ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1763918195391/bccc3817-0cbd-4da5-9881-c06e0f0f22bc.png align="center") From the image above, you can see that I have `(.venv)` that shows up when I’m in a virtual environment, `[release]` to show the `git branch` I’m in, and `{default}` to indicate the Docker Context I’m in. They’re colored differently so I can quickly differentiate them. The `$` is colored differently, too, purely just for style. You can do the same by editing the `PS1` environment variable in `~/.bashrc`. Here’s what mine looks like: ```bash if [ "$color_prompt" = yes ]; then PS1='$(git rev-parse --abbrev-ref HEAD 2>/dev/null | sed "s/^/\[\033[31m\][/;s/$/]\[\033[00m\]/")$(docker context show 2>/dev/null | sed "s/^/\[\033[38;2;0;127;255m\]{/;s/$/}\[\033[00m\]/")\n\[\033[01;32m\]{\u}\[\033[00m\]\n\[\033[38;5;214m\]$ \[\033[00m\]' else PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ ' fi ``` ### Links / further reading * Official `uv` docs & installation: [https://docs.astral.sh/uv/getting-started/installation/](https://docs.astral.sh/uv/getting-started/installation/) * `uv` overview & features (docs/home): https://docs.astral.sh/uv/ * RealPython: Managing projects with `uv` (tutorial): [https://realpython.com/python-uv/](https://realpython.com/python-uv/) * GitHub repo (source & CLI reference): [https://github.com/astral-sh/uv](https://github.com/astral-sh/uv) * Bash basics: [https://github.com/denysdovhan/bash-handbook](https://github.com/denysdovhan/bash-handbook) --- <sub>Originally posted at [blog.theolujay.dev](https://theolujay.hashnode.dev/practical-habits-for-productive-python-development)</sub>
json metadata{"app":"peakd/2025.12.9","format":"markdown","tags":["dev","python","bash","uv","backend","software","tech"],"users":[],"image":["https://cdn.hashnode.com/res/hashnode/image/upload/v1763918195391/bccc3817-0cbd-4da5-9881-c06e0f0f22bc.png"]}
parent author
parent permlinkdev
permlinkpractical-habits-for-productive-python-development
titlePractical Habits for Productive Python Development
Transaction InfoBlock #102497023/Trx c1556386551d9a86ef2f831ee3e7ca0be1a995a5
View Raw JSON Data
{
  "block": 102497023,
  "op": [
    "comment",
    {
      "author": "theolujay",
      "body": "99.99% efficiency sounds great on paper. In practice you’re usually one better tool or one small habit away from not wasting an hour on installs, CI runs, or repetitive terminal typing. Here are three practical changes I use that save time every day.\n\n## 1) Use `uv` instead of raw `pip`/`venv`\n\n`uv` is a modern, Rust-powered package & project manager that combines `virtualenv`, `pip`, and `pipx`, and it’s really fast. On projects with many dependencies, you’ll see massive wins with it.\n\nHere’s a quick benchmark takeaway: on one of my projects `pip install -r requirements.txt` took ~150s while `uv pip install` completed in ~1s. It’s needless to say you start projects quicker and your app builds are significantly faster.\n\n**Install uv**\n\n* macOS / Linux (installer script):\n    \n\n```bash\ncurl -LsSf https://astral.sh/uv/install.sh | sh\n```\n\n* Homebrew:\n    \n\n```bash\nbrew install uv\n```\n\n* Windows (PowerShell installer / winget / scoop) — see official docs for options and latest instructions. (\\[Astral Docs\\]\\[2\\])\n    \n\n**Useful \\`uv\\` commands**\n\n```bash\n# create venv (uses .venv by default)\nuv venv\n\n# install packages from pyproject or requirements via uv\nuv pip install -r requirements.txt\n\n# install only non-dev groups from pyproject\nuv sync --no-dev\n\n# add packages to groups\nuv add --group dev --group test\n```\n\nI can then run a command like the one below to install just the main packages.\n\n```plaintext\nuv sync --no-dev\n```\n\nOr if I wanted specific (or all) groups, I could do that too.\n\n```plaintext\nuv add --group dev --group test\n```\n\nThere are many more interesting ways to use `uv`, like running some commands faster, but that’ll be in a future post.\n\n## 2) Bash aliases\n\nStop retyping long commands. You could put frequently used aliases in `~/.bash_aliases` (or `~/.bashrc`) and use them instead.\n\nPractical examples I use\n\n```bash\nalias ptree='tree -I \"__pycache__|*.pyc|.venv|.git|node_modules\" --dirsfirst -L 3'\nalias py='python'\nalias pyma='python3 manage.py'\nalias entervenv='source .venv/bin/activate'\nalias dc='docker compose'\nalias gcm='git commit -m'\n```\n\n## 3) Small scripts &gt; manual workflows\n\nUpdating secrets in a running Swarm stack, for example, is a multi-step, error-prone process. Although it’s not something you’d frequently do, it’s all the same a tedious process. Wrap it in a script so you don’t do it wrong at 3 AM.\n\n```bash\n#!/bin/bash\n\n# Usage: ./update-secret.sh secret-name secret-value [stack-name]\n\nset -euo pipefail\n\nSECRET_NAME=$1\nSECRET_VALUE=$2\nSTACK_NAME=${3:-vmlc-prod}\n\nif [ -z \"$SECRET_NAME\" ] || [ -z \"$SECRET_VALUE\" ]; then\n    echo \"Usage: $0 <secret_name> <secret_value> [stack_name]\"\n    exit 1\nfi\n\nTEMP_SECRET_NAME=\"${SECRET_NAME}-temp\"\n\nprintf \"%s\" \"$SECRET_VALUE\" | docker secret create \"$TEMP_SECRET_NAME\" -\n\nfor service in db-migrate django celery_beat celery_worker; do\n    docker service update \\\n      --secret-rm \"$SECRET_NAME\" \\\n      --secret-add \"source=$TEMP_SECRET_NAME,target=$SECRET_NAME\" \\\n      \"${STACK_NAME}_${service}\" > /dev/null\ndone\n\nsleep 10\ndocker secret rm \"$SECRET_NAME\"\nprintf \"%s\" \"$SECRET_VALUE\" | docker secret create \"$SECRET_NAME\" -\n\nfor service in db-migrate django celery_beat celery_worker; do\n    docker service update \\\n      --secret-rm \"$TEMP_SECRET_NAME\" \\\n      --secret-add \"source=$SECRET_NAME,target=$SECRET_NAME\" \\\n      \"${STACK_NAME}_${service}\" > /dev/null\ndone\n\nsleep 10\ndocker secret rm \"$TEMP_SECRET_NAME\"\necho \"Done! Secret $SECRET_NAME updated successfully.\"\n```\n\nIf you copy this, adapt the \\`service\\` list to match your stack and test on staging first.\n\n### Bonus: prompt info at-a-glance\n\nAdd PS1 bits so your shell prompt shows whether you’re in a venv, the current git branch, and Docker context. There are various ways to customize it.\n\n![](https://cdn.hashnode.com/res/hashnode/image/upload/v1763918195391/bccc3817-0cbd-4da5-9881-c06e0f0f22bc.png align=\"center\")\n\nFrom the image above, you can see that I have `(.venv)` that shows up when I’m in a virtual environment, `[release]` to show the `git branch` I’m in, and `{default}` to indicate the Docker Context I’m in. They’re colored differently so I can quickly differentiate them. The `$` is colored differently, too, purely just for style. You can do the same by editing the `PS1` environment variable in `~/.bashrc`. Here’s what mine looks like:\n\n```bash\nif [ \"$color_prompt\" = yes ]; then\n    PS1='$(git rev-parse --abbrev-ref HEAD 2>/dev/null | sed \"s/^/\\[\\033[31m\\][/;s/$/]\\[\\033[00m\\]/\")$(docker context show 2>/dev/null | sed \"s/^/\\[\\033[38;2;0;127;255m\\]{/;s/$/}\\[\\033[00m\\]/\")\\n\\[\\033[01;32m\\]{\\u}\\[\\033[00m\\]\\n\\[\\033[38;5;214m\\]$ \\[\\033[00m\\]'\nelse\n    PS1='${debian_chroot:+($debian_chroot)}\\u@\\h:\\w\\$ '\nfi\n```\n\n### Links / further reading\n\n* Official `uv` docs & installation: [https://docs.astral.sh/uv/getting-started/installation/](https://docs.astral.sh/uv/getting-started/installation/)\n    \n* `uv` overview & features (docs/home): https://docs.astral.sh/uv/\n    \n* RealPython: Managing projects with `uv` (tutorial): [https://realpython.com/python-uv/](https://realpython.com/python-uv/)\n    \n* GitHub repo (source & CLI reference): [https://github.com/astral-sh/uv](https://github.com/astral-sh/uv)\n    \n* Bash basics: [https://github.com/denysdovhan/bash-handbook](https://github.com/denysdovhan/bash-handbook)\n\n---\n<sub>Originally posted at [blog.theolujay.dev](https://theolujay.hashnode.dev/practical-habits-for-productive-python-development)</sub>",
      "json_metadata": "{\"app\":\"peakd/2025.12.9\",\"format\":\"markdown\",\"tags\":[\"dev\",\"python\",\"bash\",\"uv\",\"backend\",\"software\",\"tech\"],\"users\":[],\"image\":[\"https://cdn.hashnode.com/res/hashnode/image/upload/v1763918195391/bccc3817-0cbd-4da5-9881-c06e0f0f22bc.png\"]}",
      "parent_author": "",
      "parent_permlink": "dev",
      "permlink": "practical-habits-for-productive-python-development",
      "title": "Practical Habits for Productive Python Development"
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2025-12-30T13:00:03",
  "trx_id": "c1556386551d9a86ef2f831ee3e7ca0be1a995a5",
  "trx_in_block": 13,
  "virtual_op": false
}
theolujayupdated their account properties
2025/12/30 12:02:30
accounttheolujay
json metadata
memo keySTM7qhEtvNyXVg5Y4X3RxxTCbBo3DwZvAYRPEqnmGYZnnRZVcxHpY
posting{"account_auths":[["peakd.app",1]],"key_auths":[["STM51wtCNy7STzYt8VdimooXYaBRCTqhZDmTnTPb4Z13pr5vP5mh5",1]],"weight_threshold":1}
Transaction InfoBlock #102495874/Trx a0d48574ebeb499ac9e3af248db1c16fb69b3ff7
View Raw JSON Data
{
  "block": 102495874,
  "op": [
    "account_update",
    {
      "account": "theolujay",
      "json_metadata": "",
      "memo_key": "STM7qhEtvNyXVg5Y4X3RxxTCbBo3DwZvAYRPEqnmGYZnnRZVcxHpY",
      "posting": {
        "account_auths": [
          [
            "peakd.app",
            1
          ]
        ],
        "key_auths": [
          [
            "STM51wtCNy7STzYt8VdimooXYaBRCTqhZDmTnTPb4Z13pr5vP5mh5",
            1
          ]
        ],
        "weight_threshold": 1
      }
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2025-12-30T12:02:30",
  "trx_id": "a0d48574ebeb499ac9e3af248db1c16fb69b3ff7",
  "trx_in_block": 6,
  "virtual_op": false
}
theolujayupdated their account properties
2025/12/30 11:57:54
accounttheolujay
extensions[]
json metadata
posting json metadata{"profile":{"name":"Joseph Ezekiel","about":"Software Engineer. Building stuff. Making progress.","location":"Galvan Prime","website":"theolujay.dev","profile_image":"https://files.peakd.com/file/peakd-hive/theolujay/1767023094352.jpg","cover_image":"https://files.peakd.com/file/peakd-hive/theolujay/1767023214941.jpg","version":2,"portfolio":"enabled"}}
Transaction InfoBlock #102495782/Trx 98dfa9eb68278064bdd2632971b316edcc9c7dcc
View Raw JSON Data
{
  "block": 102495782,
  "op": [
    "account_update2",
    {
      "account": "theolujay",
      "extensions": [],
      "json_metadata": "",
      "posting_json_metadata": "{\"profile\":{\"name\":\"Joseph Ezekiel\",\"about\":\"Software Engineer. Building stuff. Making progress.\",\"location\":\"Galvan Prime\",\"website\":\"theolujay.dev\",\"profile_image\":\"https://files.peakd.com/file/peakd-hive/theolujay/1767023094352.jpg\",\"cover_image\":\"https://files.peakd.com/file/peakd-hive/theolujay/1767023214941.jpg\",\"version\":2,\"portfolio\":\"enabled\"}}"
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2025-12-30T11:57:54",
  "trx_id": "98dfa9eb68278064bdd2632971b316edcc9c7dcc",
  "trx_in_block": 9,
  "virtual_op": false
}
magic.byteeffective vote applied for @theolujay / initial-commit
2025/12/30 08:41:00
authortheolujay
pending payout0.037 HBD
permlinkinitial-commit
rshares0
total vote weight307221995089
votermagic.byte
weight0 (0.00%)
Transaction InfoBlock #102491855/Trx 1793ce9723d742ea693f344959a5c808940c0aad
View Raw JSON Data
{
  "block": 102491855,
  "op": [
    "effective_comment_vote",
    {
      "author": "theolujay",
      "pending_payout": "0.037 HBD",
      "permlink": "initial-commit",
      "rshares": 0,
      "total_vote_weight": 307221995089,
      "voter": "magic.byte",
      "weight": 0
    }
  ],
  "op_in_trx": 1,
  "timestamp": "2025-12-30T08:41:00",
  "trx_id": "1793ce9723d742ea693f344959a5c808940c0aad",
  "trx_in_block": 12,
  "virtual_op": true
}
2025/12/30 08:41:00
authortheolujay
permlinkinitial-commit
votermagic.byte
weight10000 (100.00%)
Transaction InfoBlock #102491855/Trx 1793ce9723d742ea693f344959a5c808940c0aad
View Raw JSON Data
{
  "block": 102491855,
  "op": [
    "vote",
    {
      "author": "theolujay",
      "permlink": "initial-commit",
      "voter": "magic.byte",
      "weight": 10000
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2025-12-30T08:41:00",
  "trx_id": "1793ce9723d742ea693f344959a5c808940c0aad",
  "trx_in_block": 12,
  "virtual_op": false
}
lolz.byteeffective vote applied for @theolujay / initial-commit
2025/12/30 06:14:30
authortheolujay
pending payout0.036 HBD
permlinkinitial-commit
rshares947035228
total vote weight307221995089
voterlolz.byte
weight947035228
Transaction InfoBlock #102488931/Trx 0a3b92a32d3693580759434b602402a76e87df9f
View Raw JSON Data
{
  "block": 102488931,
  "op": [
    "effective_comment_vote",
    {
      "author": "theolujay",
      "pending_payout": "0.036 HBD",
      "permlink": "initial-commit",
      "rshares": 947035228,
      "total_vote_weight": 307221995089,
      "voter": "lolz.byte",
      "weight": 947035228
    }
  ],
  "op_in_trx": 1,
  "timestamp": "2025-12-30T06:14:30",
  "trx_id": "0a3b92a32d3693580759434b602402a76e87df9f",
  "trx_in_block": 6,
  "virtual_op": true
}
2025/12/30 06:14:30
authortheolujay
permlinkinitial-commit
voterlolz.byte
weight10000 (100.00%)
Transaction InfoBlock #102488931/Trx 0a3b92a32d3693580759434b602402a76e87df9f
View Raw JSON Data
{
  "block": 102488931,
  "op": [
    "vote",
    {
      "author": "theolujay",
      "permlink": "initial-commit",
      "voter": "lolz.byte",
      "weight": 10000
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2025-12-30T06:14:30",
  "trx_id": "0a3b92a32d3693580759434b602402a76e87df9f",
  "trx_in_block": 6,
  "virtual_op": false
}
magicfingerzeffective vote applied for @theolujay / initial-commit
2025/12/29 16:37:51
authortheolujay
pending payout0.035 HBD
permlinkinitial-commit
rshares12591670289
total vote weight306274959861
votermagicfingerz
weight12591670289
Transaction InfoBlock #102472632/Trx f6e1151fed16069e6e35b9bbf07351e1304d32d5
View Raw JSON Data
{
  "block": 102472632,
  "op": [
    "effective_comment_vote",
    {
      "author": "theolujay",
      "pending_payout": "0.035 HBD",
      "permlink": "initial-commit",
      "rshares": 12591670289,
      "total_vote_weight": 306274959861,
      "voter": "magicfingerz",
      "weight": 12591670289
    }
  ],
  "op_in_trx": 1,
  "timestamp": "2025-12-29T16:37:51",
  "trx_id": "f6e1151fed16069e6e35b9bbf07351e1304d32d5",
  "trx_in_block": 7,
  "virtual_op": true
}
2025/12/29 16:37:51
authortheolujay
permlinkinitial-commit
votermagicfingerz
weight1000 (10.00%)
Transaction InfoBlock #102472632/Trx f6e1151fed16069e6e35b9bbf07351e1304d32d5
View Raw JSON Data
{
  "block": 102472632,
  "op": [
    "vote",
    {
      "author": "theolujay",
      "permlink": "initial-commit",
      "voter": "magicfingerz",
      "weight": 1000
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2025-12-29T16:37:51",
  "trx_id": "f6e1151fed16069e6e35b9bbf07351e1304d32d5",
  "trx_in_block": 7,
  "virtual_op": false
}
wizjeffective vote applied for @theolujay / initial-commit
2025/12/29 16:37:51
authortheolujay
pending payout0.034 HBD
permlinkinitial-commit
rshares1108493109
total vote weight293683289572
voterwizj
weight1108493109
Transaction InfoBlock #102472632/Trx dcece4ef8af0e59f714ee50345a101632029e404
View Raw JSON Data
{
  "block": 102472632,
  "op": [
    "effective_comment_vote",
    {
      "author": "theolujay",
      "pending_payout": "0.034 HBD",
      "permlink": "initial-commit",
      "rshares": 1108493109,
      "total_vote_weight": 293683289572,
      "voter": "wizj",
      "weight": 1108493109
    }
  ],
  "op_in_trx": 1,
  "timestamp": "2025-12-29T16:37:51",
  "trx_id": "dcece4ef8af0e59f714ee50345a101632029e404",
  "trx_in_block": 5,
  "virtual_op": true
}
wizjupvoted (10.00%) @theolujay / initial-commit
2025/12/29 16:37:51
authortheolujay
permlinkinitial-commit
voterwizj
weight1000 (10.00%)
Transaction InfoBlock #102472632/Trx dcece4ef8af0e59f714ee50345a101632029e404
View Raw JSON Data
{
  "block": 102472632,
  "op": [
    "vote",
    {
      "author": "theolujay",
      "permlink": "initial-commit",
      "voter": "wizj",
      "weight": 1000
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2025-12-29T16:37:51",
  "trx_id": "dcece4ef8af0e59f714ee50345a101632029e404",
  "trx_in_block": 5,
  "virtual_op": false
}
shopnilhasaneffective vote applied for @theolujay / initial-commit
2025/12/29 16:37:18
authortheolujay
pending payout0.034 HBD
permlinkinitial-commit
rshares5482418777
total vote weight292574796463
votershopnilhasan
weight5482418777
Transaction InfoBlock #102472621/Trx c98ec1f07a6bcd4a9cfbe376b1241cc0292e37c5
View Raw JSON Data
{
  "block": 102472621,
  "op": [
    "effective_comment_vote",
    {
      "author": "theolujay",
      "pending_payout": "0.034 HBD",
      "permlink": "initial-commit",
      "rshares": 5482418777,
      "total_vote_weight": 292574796463,
      "voter": "shopnilhasan",
      "weight": 5482418777
    }
  ],
  "op_in_trx": 1,
  "timestamp": "2025-12-29T16:37:18",
  "trx_id": "c98ec1f07a6bcd4a9cfbe376b1241cc0292e37c5",
  "trx_in_block": 21,
  "virtual_op": true
}
2025/12/29 16:37:18
authortheolujay
permlinkinitial-commit
votershopnilhasan
weight4000 (40.00%)
Transaction InfoBlock #102472621/Trx c98ec1f07a6bcd4a9cfbe376b1241cc0292e37c5
View Raw JSON Data
{
  "block": 102472621,
  "op": [
    "vote",
    {
      "author": "theolujay",
      "permlink": "initial-commit",
      "voter": "shopnilhasan",
      "weight": 4000
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2025-12-29T16:37:18",
  "trx_id": "c98ec1f07a6bcd4a9cfbe376b1241cc0292e37c5",
  "trx_in_block": 21,
  "virtual_op": false
}
ksameffective vote applied for @theolujay / initial-commit
2025/12/29 16:37:18
authortheolujay
pending payout0.033 HBD
permlinkinitial-commit
rshares806099932
total vote weight287092377686
voterksam
weight806099932
Transaction InfoBlock #102472621/Trx 735fe9cc2d2137ffefb5cf8771332518ab4626b4
View Raw JSON Data
{
  "block": 102472621,
  "op": [
    "effective_comment_vote",
    {
      "author": "theolujay",
      "pending_payout": "0.033 HBD",
      "permlink": "initial-commit",
      "rshares": 806099932,
      "total_vote_weight": 287092377686,
      "voter": "ksam",
      "weight": 806099932
    }
  ],
  "op_in_trx": 1,
  "timestamp": "2025-12-29T16:37:18",
  "trx_id": "735fe9cc2d2137ffefb5cf8771332518ab4626b4",
  "trx_in_block": 20,
  "virtual_op": true
}
ksamupvoted (20.00%) @theolujay / initial-commit
2025/12/29 16:37:18
authortheolujay
permlinkinitial-commit
voterksam
weight2000 (20.00%)
Transaction InfoBlock #102472621/Trx 735fe9cc2d2137ffefb5cf8771332518ab4626b4
View Raw JSON Data
{
  "block": 102472621,
  "op": [
    "vote",
    {
      "author": "theolujay",
      "permlink": "initial-commit",
      "voter": "ksam",
      "weight": 2000
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2025-12-29T16:37:18",
  "trx_id": "735fe9cc2d2137ffefb5cf8771332518ab4626b4",
  "trx_in_block": 20,
  "virtual_op": false
}
hopestylisteffective vote applied for @theolujay / initial-commit
2025/12/29 16:36:48
authortheolujay
pending payout0.033 HBD
permlinkinitial-commit
rshares286286277754
total vote weight286286277754
voterhopestylist
weight286286277754
Transaction InfoBlock #102472611/Trx 62776be7da980dccf19961255954e100827c465c
View Raw JSON Data
{
  "block": 102472611,
  "op": [
    "effective_comment_vote",
    {
      "author": "theolujay",
      "pending_payout": "0.033 HBD",
      "permlink": "initial-commit",
      "rshares": 286286277754,
      "total_vote_weight": 286286277754,
      "voter": "hopestylist",
      "weight": 286286277754
    }
  ],
  "op_in_trx": 1,
  "timestamp": "2025-12-29T16:36:48",
  "trx_id": "62776be7da980dccf19961255954e100827c465c",
  "trx_in_block": 8,
  "virtual_op": true
}
2025/12/29 16:36:48
authortheolujay
permlinkinitial-commit
voterhopestylist
weight10000 (100.00%)
Transaction InfoBlock #102472611/Trx 62776be7da980dccf19961255954e100827c465c
View Raw JSON Data
{
  "block": 102472611,
  "op": [
    "vote",
    {
      "author": "theolujay",
      "permlink": "initial-commit",
      "voter": "hopestylist",
      "weight": 10000
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2025-12-29T16:36:48",
  "trx_id": "62776be7da980dccf19961255954e100827c465c",
  "trx_in_block": 8,
  "virtual_op": false
}
theolujaypublished a new post: initial-commit
2025/12/29 16:36:27
authortheolujay
bodyBeen a couple of months. I'm the same @olujay. > I build and maintain backend systems for web applications, spanning business logic, infrastructure, and payment systems, with attention to how these systems are deployed and operated. Haven't written articles in so long, and I'm picking it up again. Content I'll share here will be on software engineering and stuff revolving my work. Activity might pick up in the new year, but I do have a few articles on [blog.theolujay.dev](https://blog.theolujay.dev) that I'll bring here. New posts will now likely come here first and the canonical URL embedded on my blog at Hashnode. That way, SEO for my work isn't hurt. I primarily write in Python and Go and do DevOps, so you'll see a bunch of topics on them. I'm active in a number of communities (outside here), so some non-technical posts should show up as well. <sub>[Catch me](https://x.com/theolujay)</sub>
json metadata{"app":"peakd/2025.12.9","format":"markdown","image":[],"tags":["dev","software","tech","backend","devops"],"users":["olujay."]}
parent author
parent permlinkdev
permlinkinitial-commit
titlehere again
Transaction InfoBlock #102472604/Trx 325aa6dad34f61f7ff371dc25dcabd93ba8974eb
View Raw JSON Data
{
  "block": 102472604,
  "op": [
    "comment",
    {
      "author": "theolujay",
      "body": "Been a couple of months. I'm the same @olujay.\n\n> I build and maintain backend systems for web applications, spanning business logic, infrastructure, and payment systems, with attention to how these systems are deployed and operated.\n\nHaven't written articles in so long, and I'm picking it up again. Content I'll share here will be on software engineering and stuff revolving my work. Activity might pick up in the new year, but I do have a few articles on [blog.theolujay.dev](https://blog.theolujay.dev) that I'll bring here. New posts will now likely come here first and the canonical URL embedded on my blog at Hashnode. That way, SEO for my work isn't hurt.\n\nI primarily write in Python and Go and do DevOps, so you'll see a bunch of topics on them. I'm active in a number of communities (outside here), so some non-technical posts should show up as well.\n\n<sub>[Catch me](https://x.com/theolujay)</sub>",
      "json_metadata": "{\"app\":\"peakd/2025.12.9\",\"format\":\"markdown\",\"image\":[],\"tags\":[\"dev\",\"software\",\"tech\",\"backend\",\"devops\"],\"users\":[\"olujay.\"]}",
      "parent_author": "",
      "parent_permlink": "dev",
      "permlink": "initial-commit",
      "title": "here again"
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2025-12-29T16:36:27",
  "trx_id": "325aa6dad34f61f7ff371dc25dcabd93ba8974eb",
  "trx_in_block": 12,
  "virtual_op": false
}

Account Metadata

POSTING JSON METADATA
profile{"name":"Joseph Ezekiel","about":"Software Engineer. Building stuff. Making progress.","location":"Galvan Prime","website":"theolujay.dev","profile_image":"https://files.peakd.com/file/peakd-hive/theolujay/1767023094352.jpg","cover_image":"https://files.peakd.com/file/peakd-hive/theolujay/1767023214941.jpg","version":2,"portfolio":"enabled"}
JSON METADATA
None
{
  "posting_json_metadata": {
    "profile": {
      "name": "Joseph Ezekiel",
      "about": "Software Engineer. Building stuff. Making progress.",
      "location": "Galvan Prime",
      "website": "theolujay.dev",
      "profile_image": "https://files.peakd.com/file/peakd-hive/theolujay/1767023094352.jpg",
      "cover_image": "https://files.peakd.com/file/peakd-hive/theolujay/1767023214941.jpg",
      "version": 2,
      "portfolio": "enabled"
    }
  },
  "json_metadata": {}
}

Auth Keys

Owner
Single Signature
Public Keys
STM8CYNnDbbBvbVdTkHK9R2CLNpVCSkPAELVL3odf1UpTKjX4tzUF1/1
Active
Single Signature
Public Keys
STM5WSm5PM4w1DznQzKDL6Hg5Yc7PdTK6JWeEuTY1EGTxm2MtJMat1/1
Posting
Single Signature
Public Keys
STM51wtCNy7STzYt8VdimooXYaBRCTqhZDmTnTPb4Z13pr5vP5mh51/1
App Permissions
Memo
STM7qhEtvNyXVg5Y4X3RxxTCbBo3DwZvAYRPEqnmGYZnnRZVcxHpY
{
  "owner": {
    "account_auths": [],
    "key_auths": [
      [
        "STM8CYNnDbbBvbVdTkHK9R2CLNpVCSkPAELVL3odf1UpTKjX4tzUF",
        1
      ]
    ],
    "weight_threshold": 1
  },
  "active": {
    "account_auths": [],
    "key_auths": [
      [
        "STM5WSm5PM4w1DznQzKDL6Hg5Yc7PdTK6JWeEuTY1EGTxm2MtJMat",
        1
      ]
    ],
    "weight_threshold": 1
  },
  "posting": {
    "account_auths": [
      [
        "peakd.app",
        1
      ]
    ],
    "key_auths": [
      [
        "STM51wtCNy7STzYt8VdimooXYaBRCTqhZDmTnTPb4Z13pr5vP5mh5",
        1
      ]
    ],
    "weight_threshold": 1
  },
  "memo": "STM7qhEtvNyXVg5Y4X3RxxTCbBo3DwZvAYRPEqnmGYZnnRZVcxHpY"
}

Witness Votes

0 / 30
No active witness votes.
[]