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 | HIVE |
| market_balance | 0.000HIVE | HIVE |
| savings_balance | 0.000HIVE | HIVE |
| reward_hive_balance | 0.000HIVE | HIVE |
| HIVE POWER | ||
| Own HP | 0.000HP | HP |
| Delegated Out | 0.000HP | HP |
| Delegation In | 0.000HP | HP |
| Effective Power | 0.000HP | HP |
| Reward HP (pending) | 0.725HP | HP |
| HBD | ||
| hbd_balance | 0.000HBD | HBD |
| hbd_conversions | 0.000HBD | HBD |
| hbd_market_balance | 0.000HBD | HBD |
| savings_hbd_balance | 0.000HBD | HBD |
| reward_hbd_balance | 0.000HBD | HBD |
{
"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
| name | theolujay |
| id | 2589217 |
| rank | 0 |
| reputation | 0 |
| created | 2025-09-23T13:00:18 |
| recovery_account | olujay |
| proxy | None |
| invited_by | null |
| post_count | 7 |
| comment_count | 0 |
| lifetime_vote_count | 0 |
| witnesses_voted_for | 0 |
| last_post | 2026-01-02T13:01:00 |
| last_root_post | 2026-01-02T13:01:00 |
| last_vote_time | 1970-01-01T00:00:00 |
| proxied_vsf_votes | 0, 0, 0, 0 |
| can_vote | 1 |
| voting_power | 0 |
| delayed_votes | None |
| governance_vote_expiration_ts | 1969-12-31T23:59:59 |
| balance | 0.000 HIVE |
| savings_balance | 0.000 HIVE |
| hbd_balance | 0.000 HBD |
| savings_hbd_balance | 0.000 HBD |
| vesting_shares | 0.000000 VESTS |
| delegated_vesting_shares | 0.000000 VESTS |
| received_vesting_shares | 0.000000 VESTS |
| reward_vesting_balance | 1190.867863 VESTS |
| vesting_balance | 0.000 HIVE |
| vesting_withdraw_rate | 0.000000 VESTS |
| next_vesting_withdrawal | 1969-12-31T23:59:59 |
| withdrawn | 0 |
| to_withdraw | 0 |
| withdraw_routes | 0 |
| savings_withdraw_requests | 0 |
| last_account_recovery | 1970-01-01T00:00:00 |
| reset_account | null |
| last_owner_update | 1970-01-01T00:00:00 |
| last_account_update | 2025-12-30T12:02:27 |
| mined | No |
| hbd_seconds | 0 |
| hbd_last_interest_payment | 1970-01-01T00:00:00 |
| savings_hbd_last_interest_payment | 1970-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
| Incoming | Outgoing |
|---|---|
Empty | Empty |
{
"incoming": [],
"outgoing": []
}From Date
To Date
theolujayupdated payout for multi-stage-docker-uv-python2026/01/09 13:01:00
theolujayupdated payout for multi-stage-docker-uv-python
2026/01/09 13:01:00
| author | theolujay |
| permlink | multi-stage-docker-uv-python |
| Transaction Info | Block #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-python2026/01/09 13:01:00
theolujayreceived 0.037 HBD reward share for multi-stage-docker-uv-python
2026/01/09 13:01:00
| 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 |
| Transaction Info | Block #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-python2026/01/09 13:01:00
theolujayreceived 0.178 HP author reward for @theolujay / multi-stage-docker-uv-python
2026/01/09 13:01:00
| 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 |
| Transaction Info | Block #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
}theolujayupdated payout for pg-backup-docker-s3-slack2026/01/08 13:00:00
theolujayupdated payout for pg-backup-docker-s3-slack
2026/01/08 13:00:00
| author | theolujay |
| permlink | pg-backup-docker-s3-slack |
| Transaction Info | Block #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-slack2026/01/08 13:00:00
theolujayreceived 0.029 HBD reward share for pg-backup-docker-s3-slack
2026/01/08 13:00:00
| 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 |
| Transaction Info | Block #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-slack2026/01/08 13:00:00
theolujayreceived 0.148 HP author reward for @theolujay / pg-backup-docker-s3-slack
2026/01/08 13:00:00
| 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 |
| Transaction Info | Block #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-t85dzh2026/01/07 18:57:42
theolujayupdated payout for re-hivebuzz-t85dzh
2026/01/07 18:57:42
| author | theolujay |
| permlink | re-hivebuzz-t85dzh |
| Transaction Info | Block #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
}theolujayupdated payout for how-cloudflare-fixed-my-geo-locked-blog2026/01/07 13:00:00
theolujayupdated payout for how-cloudflare-fixed-my-geo-locked-blog
2026/01/07 13:00:00
| author | theolujay |
| permlink | how-cloudflare-fixed-my-geo-locked-blog |
| Transaction Info | Block #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-blog2026/01/07 13:00:00
theolujayreceived 0.042 HBD reward share for how-cloudflare-fixed-my-geo-locked-blog
2026/01/07 13:00:00
| 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 |
| Transaction Info | Block #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-blog2026/01/07 13:00:00
theolujayreceived 0.217 HP author reward for @theolujay / how-cloudflare-fixed-my-geo-locked-blog
2026/01/07 13:00:00
| 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 |
| Transaction Info | Block #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
}theolujayupdated payout for practical-habits-for-productive-python-development2026/01/06 13:00:00
theolujayupdated payout for practical-habits-for-productive-python-development
2026/01/06 13:00:00
| author | theolujay |
| permlink | practical-habits-for-productive-python-development |
| Transaction Info | Block #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-commit2026/01/05 16:24:00
theolujayupdated payout for initial-commit
2026/01/05 16:24:00
| author | theolujay |
| permlink | initial-commit |
| Transaction Info | Block #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-commit2026/01/05 16:24:00
theolujayreceived 0.036 HBD reward share for initial-commit
2026/01/05 16:24:00
| 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 |
| Transaction Info | Block #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-commit2026/01/05 16:24:00
theolujayreceived 0.191 HP author reward for @theolujay / initial-commit
2026/01/05 16:24:00
| 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 |
| Transaction Info | Block #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-slack2026/01/04 06:28:09
magic.byteeffective vote applied for @theolujay / pg-backup-docker-s3-slack
2026/01/04 06:28:09
| author | theolujay |
| pending payout | 0.028 HBD |
| permlink | pg-backup-docker-s3-slack |
| rshares | 0 |
| total vote weight | 235731208671 |
| voter | magic.byte |
| weight | 0 (0.00%) |
| Transaction Info | Block #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
}magic.byteupvoted (100.00%) @theolujay / pg-backup-docker-s3-slack2026/01/04 06:28:09
magic.byteupvoted (100.00%) @theolujay / pg-backup-docker-s3-slack
2026/01/04 06:28:09
| author | theolujay |
| permlink | pg-backup-docker-s3-slack |
| voter | magic.byte |
| weight | 10000 (100.00%) |
| Transaction Info | Block #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
}lolz.byteeffective vote applied for @theolujay / multi-stage-docker-uv-python2026/01/02 20:49:39
lolz.byteeffective vote applied for @theolujay / multi-stage-docker-uv-python
2026/01/02 20:49:39
| author | theolujay |
| pending payout | 0.034 HBD |
| permlink | multi-stage-docker-uv-python |
| rshares | 947035228 |
| total vote weight | 282857333166 |
| voter | lolz.byte |
| weight | 947035228 |
| Transaction Info | Block #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
}lolz.byteupvoted (100.00%) @theolujay / multi-stage-docker-uv-python2026/01/02 20:49:39
lolz.byteupvoted (100.00%) @theolujay / multi-stage-docker-uv-python
2026/01/02 20:49:39
| author | theolujay |
| permlink | multi-stage-docker-uv-python |
| voter | lolz.byte |
| weight | 10000 (100.00%) |
| Transaction Info | Block #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
}magic.byteeffective vote applied for @theolujay / multi-stage-docker-uv-python2026/01/02 16:01:51
magic.byteeffective vote applied for @theolujay / multi-stage-docker-uv-python
2026/01/02 16:01:51
| author | theolujay |
| pending payout | 0.033 HBD |
| permlink | multi-stage-docker-uv-python |
| rshares | 0 |
| total vote weight | 281910297938 |
| voter | magic.byte |
| weight | 0 (0.00%) |
| Transaction Info | Block #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
}magic.byteupvoted (100.00%) @theolujay / multi-stage-docker-uv-python2026/01/02 16:01:51
magic.byteupvoted (100.00%) @theolujay / multi-stage-docker-uv-python
2026/01/02 16:01:51
| author | theolujay |
| permlink | multi-stage-docker-uv-python |
| voter | magic.byte |
| weight | 10000 (100.00%) |
| Transaction Info | Block #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-python2026/01/02 13:38:21
akdxeffective vote applied for @theolujay / multi-stage-docker-uv-python
2026/01/02 13:38:21
| author | theolujay |
| pending payout | 0.033 HBD |
| permlink | multi-stage-docker-uv-python |
| rshares | 281763154931 |
| total vote weight | 281910297938 |
| voter | akdx |
| weight | 281763154931 |
| Transaction Info | Block #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
}akdxupvoted (60.00%) @theolujay / multi-stage-docker-uv-python2026/01/02 13:38:21
akdxupvoted (60.00%) @theolujay / multi-stage-docker-uv-python
2026/01/02 13:38:21
| author | theolujay |
| permlink | multi-stage-docker-uv-python |
| voter | akdx |
| weight | 6000 (60.00%) |
| Transaction Info | Block #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
2026/01/02 13:01:12
| author | we-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 author | theolujay |
| parent permlink | multi-stage-docker-uv-python |
| permlink | re-multi-stage-docker-uv-python-1767358871650 |
| title | |
| Transaction Info | Block #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
}we-are-aieffective vote applied for @theolujay / multi-stage-docker-uv-python2026/01/02 13:01:09
we-are-aieffective vote applied for @theolujay / multi-stage-docker-uv-python
2026/01/02 13:01:09
| 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 |
| Transaction Info | Block #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
}we-are-aiupvoted (3.00%) @theolujay / multi-stage-docker-uv-python2026/01/02 13:01:09
we-are-aiupvoted (3.00%) @theolujay / multi-stage-docker-uv-python
2026/01/02 13:01:09
| author | theolujay |
| permlink | multi-stage-docker-uv-python |
| voter | we-are-ai |
| weight | 300 (3.00%) |
| Transaction Info | Block #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
}theolujayupdated options for multi-stage-docker-uv-python2026/01/02 13:01:03
theolujayupdated options for multi-stage-docker-uv-python
2026/01/02 13:01:03
| 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 |
| Transaction Info | Block #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
}theolujaypublished a new post: multi-stage-docker-uv-python2026/01/02 13:01:03
theolujaypublished a new post: multi-stage-docker-uv-python
2026/01/02 13:01:03
| 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. 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&h=840&fit=crop&crop=entropy&auto=compress,format&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 |
| Transaction Info | Block #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&h=840&fit=crop&crop=entropy&auto=compress,format&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-slack2026/01/02 01:38:57
lolz.byteeffective vote applied for @theolujay / pg-backup-docker-s3-slack
2026/01/02 01:38:57
| author | theolujay |
| pending payout | 0.029 HBD |
| permlink | pg-backup-docker-s3-slack |
| rshares | 947035228 |
| total vote weight | 235731208671 |
| voter | lolz.byte |
| weight | 947035228 |
| Transaction Info | Block #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
}lolz.byteupvoted (100.00%) @theolujay / pg-backup-docker-s3-slack2026/01/02 01:38:57
lolz.byteupvoted (100.00%) @theolujay / pg-backup-docker-s3-slack
2026/01/02 01:38:57
| author | theolujay |
| permlink | pg-backup-docker-s3-slack |
| voter | lolz.byte |
| weight | 10000 (100.00%) |
| Transaction Info | Block #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
}magic.byteeffective vote applied for @theolujay / how-cloudflare-fixed-my-geo-locked-blog2026/01/01 16:05:12
magic.byteeffective vote applied for @theolujay / how-cloudflare-fixed-my-geo-locked-blog
2026/01/01 16:05:12
| 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 (0.00%) |
| Transaction Info | Block #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
}magic.byteupvoted (100.00%) @theolujay / how-cloudflare-fixed-my-geo-locked-blog2026/01/01 16:05:12
magic.byteupvoted (100.00%) @theolujay / how-cloudflare-fixed-my-geo-locked-blog
2026/01/01 16:05:12
| author | theolujay |
| permlink | how-cloudflare-fixed-my-geo-locked-blog |
| voter | magic.byte |
| weight | 10000 (100.00%) |
| Transaction Info | Block #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
}lolz.byteeffective vote applied for @theolujay / how-cloudflare-fixed-my-geo-locked-blog2026/01/01 16:04:54
lolz.byteeffective vote applied for @theolujay / how-cloudflare-fixed-my-geo-locked-blog
2026/01/01 16:04:54
| 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 |
| Transaction Info | Block #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
}lolz.byteupvoted (100.00%) @theolujay / how-cloudflare-fixed-my-geo-locked-blog2026/01/01 16:04:54
lolz.byteupvoted (100.00%) @theolujay / how-cloudflare-fixed-my-geo-locked-blog
2026/01/01 16:04:54
| author | theolujay |
| permlink | how-cloudflare-fixed-my-geo-locked-blog |
| voter | lolz.byte |
| weight | 10000 (100.00%) |
| Transaction Info | Block #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-slack2026/01/01 13:27:18
akdxeffective vote applied for @theolujay / pg-backup-docker-s3-slack
2026/01/01 13:27:18
| author | theolujay |
| pending payout | 0.028 HBD |
| permlink | pg-backup-docker-s3-slack |
| rshares | 234784173443 |
| total vote weight | 234784173443 |
| voter | akdx |
| weight | 234784173443 |
| Transaction Info | Block #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
}akdxupvoted (50.00%) @theolujay / pg-backup-docker-s3-slack2026/01/01 13:27:18
akdxupvoted (50.00%) @theolujay / pg-backup-docker-s3-slack
2026/01/01 13:27:18
| author | theolujay |
| permlink | pg-backup-docker-s3-slack |
| voter | akdx |
| weight | 5000 (50.00%) |
| Transaction Info | Block #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
}theolujayupdated options for pg-backup-docker-s3-slack2026/01/01 13:00:03
theolujayupdated options for pg-backup-docker-s3-slack
2026/01/01 13:00:03
| 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 |
| Transaction Info | Block #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
}theolujaypublished a new post: pg-backup-docker-s3-slack2026/01/01 13:00:03
theolujaypublished a new post: pg-backup-docker-s3-slack
2026/01/01 13:00:03
| 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. 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:  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&h=840&fit=crop&crop=entropy&auto=compress,format&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 |
| Transaction Info | Block #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 \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&h=840&fit=crop&crop=entropy&auto=compress,format&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-t85dzh2025/12/31 18:57:45
theolujayupdated options for re-hivebuzz-t85dzh
2025/12/31 18:57:45
| allow curation rewards | true |
| allow votes | true |
| author | theolujay |
| extensions | [] |
| max accepted payout | 1000000.000 HBD |
| percent hbd | 0 |
| permlink | re-hivebuzz-t85dzh |
| Transaction Info | Block #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
}theolujayreplied to @hivebuzz / re-hivebuzz-t85dzh2025/12/31 18:57:45
theolujayreplied to @hivebuzz / re-hivebuzz-t85dzh
2025/12/31 18:57:45
| 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 | |
| Transaction Info | Block #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 post2025/12/31 18:56:21
theolujaydeleted a comment or post
2025/12/31 18:56:21
| author | theolujay |
| permlink | re-hivebuzz-t85dx6 |
| Transaction Info | Block #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-t85dx62025/12/31 18:56:03
theolujayupdated options for re-hivebuzz-t85dx6
2025/12/31 18:56:03
| allow curation rewards | true |
| allow votes | true |
| author | theolujay |
| extensions | [] |
| max accepted payout | 1000000.000 HBD |
| percent hbd | 0 |
| permlink | re-hivebuzz-t85dx6 |
| Transaction Info | Block #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
}theolujayreplied to @hivebuzz / re-hivebuzz-t85dx62025/12/31 18:56:03
theolujayreplied to @hivebuzz / re-hivebuzz-t85dx6
2025/12/31 18:56:03
| 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 | |
| Transaction Info | Block #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
}hivebuzzreplied to @theolujay / notify-17672061132025/12/31 18:35:15
hivebuzzreplied to @theolujay / notify-1767206113
2025/12/31 18:35:15
| author | hivebuzz |
| body | Congratulations @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 author | theolujay |
| parent permlink | how-cloudflare-fixed-my-geo-locked-blog |
| permlink | notify-1767206113 |
| title | |
| Transaction Info | Block #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
}mayor-001effective vote applied for @theolujay / how-cloudflare-fixed-my-geo-locked-blog2025/12/31 15:55:15
mayor-001effective vote applied for @theolujay / how-cloudflare-fixed-my-geo-locked-blog
2025/12/31 15:55:15
| 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 |
| Transaction Info | Block #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
}mayor-001upvoted (52.50%) @theolujay / how-cloudflare-fixed-my-geo-locked-blog2025/12/31 15:55:15
mayor-001upvoted (52.50%) @theolujay / how-cloudflare-fixed-my-geo-locked-blog
2025/12/31 15:55:15
| author | theolujay |
| permlink | how-cloudflare-fixed-my-geo-locked-blog |
| voter | mayor-001 |
| weight | 5250 (52.50%) |
| Transaction Info | Block #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
}theolujaypublished a new post: how-cloudflare-fixed-my-geo-locked-blog2025/12/31 15:50:24
theolujaypublished a new post: how-cloudflare-fixed-my-geo-locked-blog
2025/12/31 15:50:24
| author | theolujay |
| 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 permlink | dev |
| permlink | how-cloudflare-fixed-my-geo-locked-blog |
| title | How Cloudflare Fixed My “Geo-Locked” Blog |
| Transaction Info | Block #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
}erica005effective vote applied for @theolujay / how-cloudflare-fixed-my-geo-locked-blog2025/12/31 13:52:33
erica005effective vote applied for @theolujay / how-cloudflare-fixed-my-geo-locked-blog
2025/12/31 13:52:33
| 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 |
| Transaction Info | Block #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
}erica005upvoted (100.00%) @theolujay / how-cloudflare-fixed-my-geo-locked-blog2025/12/31 13:52:33
erica005upvoted (100.00%) @theolujay / how-cloudflare-fixed-my-geo-locked-blog
2025/12/31 13:52:33
| author | theolujay |
| permlink | how-cloudflare-fixed-my-geo-locked-blog |
| voter | erica005 |
| weight | 10000 (100.00%) |
| Transaction Info | Block #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
}akdxeffective vote applied for @theolujay / how-cloudflare-fixed-my-geo-locked-blog2025/12/31 13:35:42
akdxeffective vote applied for @theolujay / how-cloudflare-fixed-my-geo-locked-blog
2025/12/31 13:35:42
| 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 |
| Transaction Info | Block #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
}akdxupvoted (60.00%) @theolujay / how-cloudflare-fixed-my-geo-locked-blog2025/12/31 13:35:42
akdxupvoted (60.00%) @theolujay / how-cloudflare-fixed-my-geo-locked-blog
2025/12/31 13:35:42
| author | theolujay |
| permlink | how-cloudflare-fixed-my-geo-locked-blog |
| voter | akdx |
| weight | 6000 (60.00%) |
| Transaction Info | Block #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
}theolujayupdated options for how-cloudflare-fixed-my-geo-locked-blog2025/12/31 13:00:03
theolujayupdated options for how-cloudflare-fixed-my-geo-locked-blog
2025/12/31 13:00:03
| 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 |
| Transaction Info | Block #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
}theolujaypublished a new post: how-cloudflare-fixed-my-geo-locked-blog2025/12/31 13:00:03
theolujaypublished a new post: how-cloudflare-fixed-my-geo-locked-blog
2025/12/31 13:00:03
| 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. 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 permlink | dev |
| permlink | how-cloudflare-fixed-my-geo-locked-blog |
| title | How Cloudflare Fixed My “Geo-Locked” Blog |
| Transaction Info | Block #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
}lolz.byteeffective vote applied for @theolujay / practical-habits-for-productive-python-development2025/12/30 15:51:30
lolz.byteeffective vote applied for @theolujay / practical-habits-for-productive-python-development
2025/12/30 15:51:30
| 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 |
| Transaction Info | Block #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
}lolz.byteupvoted (100.00%) @theolujay / practical-habits-for-productive-python-development2025/12/30 15:51:30
lolz.byteupvoted (100.00%) @theolujay / practical-habits-for-productive-python-development
2025/12/30 15:51:30
| author | theolujay |
| permlink | practical-habits-for-productive-python-development |
| voter | lolz.byte |
| weight | 10000 (100.00%) |
| Transaction Info | Block #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-commit2025/12/30 13:00:39
theolujaypublished a new post: initial-commit
2025/12/30 13:00:39
| author | theolujay |
| 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 permlink | dev |
| permlink | initial-commit |
| title | here again |
| Transaction Info | Block #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
}theolujayupdated options for practical-habits-for-productive-python-development2025/12/30 13:00:03
theolujayupdated options for practical-habits-for-productive-python-development
2025/12/30 13:00:03
| 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 |
| Transaction Info | Block #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
}theolujaypublished a new post: practical-habits-for-productive-python-development2025/12/30 13:00:03
theolujaypublished a new post: practical-habits-for-productive-python-development
2025/12/30 13:00:03
| 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. ## 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 > 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.  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 permlink | dev |
| permlink | practical-habits-for-productive-python-development |
| title | Practical Habits for Productive Python Development |
| Transaction Info | Block #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 > 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\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 properties2025/12/30 12:02:30
theolujayupdated their account properties
2025/12/30 12:02:30
| account | theolujay |
| json metadata | |
| memo key | STM7qhEtvNyXVg5Y4X3RxxTCbBo3DwZvAYRPEqnmGYZnnRZVcxHpY |
| posting | {"account_auths":[["peakd.app",1]],"key_auths":[["STM51wtCNy7STzYt8VdimooXYaBRCTqhZDmTnTPb4Z13pr5vP5mh5",1]],"weight_threshold":1} |
| Transaction Info | Block #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 properties2025/12/30 11:57:54
theolujayupdated their account properties
2025/12/30 11:57:54
| 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"}} |
| Transaction Info | Block #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-commit2025/12/30 08:41:00
magic.byteeffective vote applied for @theolujay / initial-commit
2025/12/30 08:41:00
| author | theolujay |
| pending payout | 0.037 HBD |
| permlink | initial-commit |
| rshares | 0 |
| total vote weight | 307221995089 |
| voter | magic.byte |
| weight | 0 (0.00%) |
| Transaction Info | Block #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
}magic.byteupvoted (100.00%) @theolujay / initial-commit2025/12/30 08:41:00
magic.byteupvoted (100.00%) @theolujay / initial-commit
2025/12/30 08:41:00
| author | theolujay |
| permlink | initial-commit |
| voter | magic.byte |
| weight | 10000 (100.00%) |
| Transaction Info | Block #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-commit2025/12/30 06:14:30
lolz.byteeffective vote applied for @theolujay / initial-commit
2025/12/30 06:14:30
| author | theolujay |
| pending payout | 0.036 HBD |
| permlink | initial-commit |
| rshares | 947035228 |
| total vote weight | 307221995089 |
| voter | lolz.byte |
| weight | 947035228 |
| Transaction Info | Block #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
}lolz.byteupvoted (100.00%) @theolujay / initial-commit2025/12/30 06:14:30
lolz.byteupvoted (100.00%) @theolujay / initial-commit
2025/12/30 06:14:30
| author | theolujay |
| permlink | initial-commit |
| voter | lolz.byte |
| weight | 10000 (100.00%) |
| Transaction Info | Block #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-commit2025/12/29 16:37:51
magicfingerzeffective vote applied for @theolujay / initial-commit
2025/12/29 16:37:51
| author | theolujay |
| pending payout | 0.035 HBD |
| permlink | initial-commit |
| rshares | 12591670289 |
| total vote weight | 306274959861 |
| voter | magicfingerz |
| weight | 12591670289 |
| Transaction Info | Block #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
}magicfingerzupvoted (10.00%) @theolujay / initial-commit2025/12/29 16:37:51
magicfingerzupvoted (10.00%) @theolujay / initial-commit
2025/12/29 16:37:51
| author | theolujay |
| permlink | initial-commit |
| voter | magicfingerz |
| weight | 1000 (10.00%) |
| Transaction Info | Block #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-commit2025/12/29 16:37:51
wizjeffective vote applied for @theolujay / initial-commit
2025/12/29 16:37:51
| author | theolujay |
| pending payout | 0.034 HBD |
| permlink | initial-commit |
| rshares | 1108493109 |
| total vote weight | 293683289572 |
| voter | wizj |
| weight | 1108493109 |
| Transaction Info | Block #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-commit2025/12/29 16:37:51
wizjupvoted (10.00%) @theolujay / initial-commit
2025/12/29 16:37:51
| author | theolujay |
| permlink | initial-commit |
| voter | wizj |
| weight | 1000 (10.00%) |
| Transaction Info | Block #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-commit2025/12/29 16:37:18
shopnilhasaneffective vote applied for @theolujay / initial-commit
2025/12/29 16:37:18
| author | theolujay |
| pending payout | 0.034 HBD |
| permlink | initial-commit |
| rshares | 5482418777 |
| total vote weight | 292574796463 |
| voter | shopnilhasan |
| weight | 5482418777 |
| Transaction Info | Block #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
}shopnilhasanupvoted (40.00%) @theolujay / initial-commit2025/12/29 16:37:18
shopnilhasanupvoted (40.00%) @theolujay / initial-commit
2025/12/29 16:37:18
| author | theolujay |
| permlink | initial-commit |
| voter | shopnilhasan |
| weight | 4000 (40.00%) |
| Transaction Info | Block #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-commit2025/12/29 16:37:18
ksameffective vote applied for @theolujay / initial-commit
2025/12/29 16:37:18
| author | theolujay |
| pending payout | 0.033 HBD |
| permlink | initial-commit |
| rshares | 806099932 |
| total vote weight | 287092377686 |
| voter | ksam |
| weight | 806099932 |
| Transaction Info | Block #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-commit2025/12/29 16:37:18
ksamupvoted (20.00%) @theolujay / initial-commit
2025/12/29 16:37:18
| author | theolujay |
| permlink | initial-commit |
| voter | ksam |
| weight | 2000 (20.00%) |
| Transaction Info | Block #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-commit2025/12/29 16:36:48
hopestylisteffective vote applied for @theolujay / initial-commit
2025/12/29 16:36:48
| author | theolujay |
| pending payout | 0.033 HBD |
| permlink | initial-commit |
| rshares | 286286277754 |
| total vote weight | 286286277754 |
| voter | hopestylist |
| weight | 286286277754 |
| Transaction Info | Block #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
}hopestylistupvoted (100.00%) @theolujay / initial-commit2025/12/29 16:36:48
hopestylistupvoted (100.00%) @theolujay / initial-commit
2025/12/29 16:36:48
| author | theolujay |
| permlink | initial-commit |
| voter | hopestylist |
| weight | 10000 (100.00%) |
| Transaction Info | Block #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-commit2025/12/29 16:36:27
theolujaypublished a new post: initial-commit
2025/12/29 16:36:27
| author | theolujay |
| body | Been 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 permlink | dev |
| permlink | initial-commit |
| title | here again |
| Transaction Info | Block #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
}Manabar
Voting Power0.00%
Downvote Power0.00%
Resource Credits100.00%
Reputation Progress0.00%
{
"voting_manabar": {
"current_mana": 0,
"last_update_time": 1758632415
},
"downvote_manabar": {
"current_mana": 0,
"last_update_time": 1758632415
},
"rc_account": {
"account": "theolujay",
"delegated_rc": 0,
"max_rc": 21399149235,
"max_rc_creation_adjustment": {
"amount": "4969149235",
"nai": "@@000000037",
"precision": 6
},
"rc_manabar": {
"current_mana": 7357266602,
"last_update_time": 1767358860
},
"received_delegated_rc": 16430000000
}
}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
@peakd.app1/1
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.
[]
